feat: 添加pinia状态管理库和Tabbar组件

- 添加pinia状态管理库和Tabbar组件
- 引入axios和fant-axios-adapter
- 在main.ts中使用pinia和persist插件
- 在App.vue中修改标题
- 在useUser.ts中添加logout方法
- 添加persist.ts文件
- 修改page-wrapper.vue和TabBar.vue中的代码
- 修改index.vue和login.vue中的代码
This commit is contained in:
Timothy Yin 2024-09-19 00:13:56 +08:00
parent cf77c72dfe
commit ceb9636b42
13 changed files with 806 additions and 139 deletions

View File

@ -73,6 +73,8 @@
"@vue/runtime-core": "^3.4.21", "@vue/runtime-core": "^3.4.21",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"add": "^2.0.6", "add": "^2.0.6",
"axios": "^1.7.7",
"fant-axios-adapter": "^0.0.6",
"sass": "^1.78.0", "sass": "^1.78.0",
"sass-loader": "10", "sass-loader": "10",
"typescript": "^4.9.4", "typescript": "^4.9.4",

654
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app"; import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
import { useTabbar } from "./stores/useTabbar";
const tab = useTabbar()
onLaunch(() => { onLaunch(() => {
console.log("App Launch"); console.log("App Launch");
tab.activeTab = 'home'
}); });
onShow(() => { onShow(() => {
console.log("App Show"); console.log("App Show");

28
src/api/BussApi.ts Normal file
View File

@ -0,0 +1,28 @@
import http from "@/http/HttpClient";
import type { User } from "@/types/api/user";
export interface LoginRequest extends Record<string, string> {
email: string;
password: string;
remember: string;
}
export default class BussApi {
static login(params: LoginRequest): Promise<{ token: string }> {
return http
.server()
.post("/login", params)
.then((res) => res.data);
}
static profile(token: string): Promise<User> {
return http
.server()
.get("/user/online", {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((res) => res.data);
}
}

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { nextTick, ref } from 'vue';
import { useRouter, useRoute } from 'uni-mini-router' import { useRouter, useRoute } from 'uni-mini-router'
import { onMounted, computed, watch } from 'vue'; import { onMounted, computed, watch } from 'vue';
import { useTabbar } from '@/stores/useTabbar'; import { useTabbar } from '@/stores/useTabbar';
@ -14,10 +14,17 @@ const props = defineProps({
const router = useRouter() const router = useRouter()
const tab = useTabbar() const tab = useTabbar()
// const isFirstTime = ref(true)
// onMounted(() => { // onMounted(() => {
// tab.activeTab = props.currentName // if (isFirstTime.value) {
// tab.activeTab = props.currentName
// isFirstTime.value = false
// }
// }) // })
const tabWhitelist = ['home', 'progress', 'my']
const nameLabelIconMap = { const nameLabelIconMap = {
home: { home: {
title: '进度查看', title: '进度查看',
@ -33,11 +40,11 @@ const nameLabelIconMap = {
} }
} }
const tabList = computed(() => router.routes.map((route: { name: keyof typeof nameLabelIconMap }) => { const tabList = computed(() => router.routes.filter((r: { name: string }) => tabWhitelist.includes(r.name)).map((route: { name: keyof typeof nameLabelIconMap }) => {
return { return {
name: route.name, name: route.name,
title: nameLabelIconMap[route.name].title, title: nameLabelIconMap[route.name]?.title,
icon: nameLabelIconMap[route.name].icon icon: nameLabelIconMap[route.name]?.icon
} }
})) }))
</script> </script>
@ -45,7 +52,8 @@ const tabList = computed(() => router.routes.map((route: { name: keyof typeof na
<template> <template>
<div> <div>
<wd-tabbar v-model="tab.activeTab" fixed safe-area-inset-bottom bordered placeholder> <wd-tabbar v-model="tab.activeTab" fixed safe-area-inset-bottom bordered placeholder>
<wd-tabbar-item v-for="(tab, i) in tabList" :name="tab.name" :title="tab.title" :icon="tab.icon" :key="i" @tap="router.pushTab({ name: tab.name })" /> <wd-tabbar-item v-for="(tab, i) in tabList" :name="tab.name" :title="tab.title" :icon="tab.icon" :key="i"
@tap="router.pushTab({ name: tab.name })" />
</wd-tabbar> </wd-tabbar>
</div> </div>
</template> </template>

View File

@ -4,14 +4,14 @@ import { useUser } from '@/stores/useUser';
import { useRouter } from 'uni-mini-router'; import { useRouter } from 'uni-mini-router';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
const router = useRouter() // const router = useRouter()
const user = useUser() // const user = useUser()
onMounted(() => { // onMounted(() => {
if (!user.userinfo) { // if (!user.userinfo) {
router.replaceAll('/pages/login/index') // router.replaceAll('/pages/login/index')
} // }
}) // })
</script> </script>
<template> <template>

114
src/http/HttpClient.ts Normal file
View File

@ -0,0 +1,114 @@
import axios from "axios";
import { uniAdapter } from "fant-axios-adapter";
export default class ApiClient {
public static server() {
const BASE_URL = "https://ppmp.fenshenzhike.com/api";
return ApiClient.create(BASE_URL);
}
public static create(baseUrl: string) {
const instance = axios.create({
withCredentials: true,
baseURL: baseUrl,
adapter: uniAdapter,
});
instance.interceptors.request.use(
(request) => {
if (request.headers) {
request.headers.set(
"Content-Type",
"application/x-www-form-urlencoded"
);
} else {
request.headers = new axios.AxiosHeaders();
request.headers.set(
"Content-Type",
"application/x-www-form-urlencoded"
);
}
request.headers.trace_id = new Date().getTime();
return request;
},
(error) => Promise.reject(error)
);
instance.interceptors.response.use(
(response) => {
if (response.data.code === 10001) {
const pages = getCurrentPages() as any[];
setTimeout(() => {
uni.showToast({ title: "登录已过期,请重新登录", icon: "none" });
}, 300);
if (
!pages[pages.length - 1].$page ||
(pages[pages.length - 1].$page &&
pages[pages.length - 1].$page.fullPath !== "/pages/login/index")
) {
uni.reLaunch({ url: "/pages/login/index" });
}
}
if (!response.data.code || response.data.code === 10000) {
return response;
} else {
const error: Record<string, any> = {};
if (response.data.code) {
error.code = response.data.code;
}
if (response.data.message) {
error.message = response.data.message;
} else {
error.message = `服务器内部错误:${response.status}`;
}
// error.response = response.data;
return Promise.reject(error);
}
},
(error) => {
if (error.status !== 0 && !error.status) {
const newError = error as any;
newError.msg = newError.errMsg || "网络错误";
return Promise.reject(newError);
}
const pages = getCurrentPages() as any[];
switch (error.status) {
case 1:
error.msg = "网络超时";
break;
case 401:
// todo 401 logout
error.msg = "请先登录";
setTimeout(() => {
uni.showToast({ title: "登录已过期,请重新登录", icon: "none" });
}, 300);
if (
!pages[pages.length - 1].$page ||
(pages[pages.length - 1].$page &&
pages[pages.length - 1].$page.fullPath !== "/pages/login/index")
) {
uni.reLaunch({ url: "/pages/login/index" });
}
break;
case 403:
error.msg = `${error.status} 禁止访问!`;
break;
case 500:
error.msg = `${error.status} 服务内部异常!`;
break;
case 502:
error.msg = `${error.status} 服务器暂不可用!`;
break;
case 503:
error.msg = `${error.status} 服务器升级中!`;
break;
case 404:
error.msg = `${error.status} 服务器无回应!`;
break;
default:
error.msg = `${error.status} 未知错误!`;
}
return Promise.reject(error);
}
);
return instance;
}
}

View File

@ -4,9 +4,12 @@ import App from "./App.vue";
import router from "./router"; import router from "./router";
import "uno.css"; import "uno.css";
import { persist } from "./stores/persist";
export function createApp() { export function createApp() {
const pinia = createPinia(); const pinia = createPinia();
pinia.use(persist);
const app = createSSRApp(App); const app = createSSRApp(App);
app.use(pinia); app.use(pinia);

View File

@ -3,8 +3,8 @@
<div class="content"> <div class="content">
<img class="logo" src="/static/logo.png" /> <img class="logo" src="/static/logo.png" />
<div class="flex flex-col items-center gap-4"> <div class="flex flex-col items-center gap-4">
<p class="title text-red-500"> <p class="title text-4xl text-neutral-300 font-bold">
Welcome XSH PPMS
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,10 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useConfig } from '@/composables/useConfig'; import BussApi from '@/api/BussApi';
import { useUser } from '@/stores/useUser';
import { useRouter } from 'uni-mini-router';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useToast } from 'wot-design-uni'; import { useToast } from 'wot-design-uni';
const router = useRouter()
const toast = useToast() const toast = useToast()
const config = useConfig() const user = useUser()
const model = reactive<{ const model = reactive<{
email: string email: string
@ -13,7 +16,7 @@ const model = reactive<{
}>({ }>({
email: '', email: '',
password: '', password: '',
remember: false remember: true
}) })
const form = ref() const form = ref()
@ -25,22 +28,27 @@ const handleSubmit = () => {
toast.loading({ toast.loading({
msg: '登录中...' msg: '登录中...'
}) })
fetch(`${config.BASE_URL}/login`, { BussApi.login({
method: 'POST', email: model.email,
headers: { 'content-type': 'application/x-www-form-urlencoded' }, password: model.password,
body: new URLSearchParams({ email: 'root@cyqsd.cn', password: 'Abc123456', remember: 'false' }) remember: model.remember + '',
}).then(res => {
user.token = res.token
toast.loading({
msg: '加载资料...'
})
BussApi.profile(user.token).then(res => {
user.userinfo = res
toast.success({ msg: '登录成功' })
setTimeout(() => {
router.pushTab('/pages/index/index')
}, 1000)
}).catch(err => {
toast.error({ msg: err.message })
})
}).catch(err => {
toast.error({ msg: err.message })
}) })
.then(res => res.json())
.then(res => {
toast.info({
msg: res.token
})
})
.catch(err => {
toast.error({
msg: err || '登录失败,未知错误'
})
})
} }
}) })
.catch((error: any) => { .catch((error: any) => {

View File

@ -1,23 +1,39 @@
<script lang="ts" setup> <script lang="ts" setup>
import BussApi from '@/api/BussApi';
import pageWrapper from '@/components/page-wrapper.vue'; import pageWrapper from '@/components/page-wrapper.vue';
import { useUser } from '@/stores/useUser';
import { useRouter } from 'uni-mini-router';
import { onMounted } from 'vue';
const router = useRouter()
const user = useUser()
const logout = () => {
user.logout()
router.replaceAll('/pages/login/index')
}
onMounted(() => {
BussApi.profile(user.token!).then(res => {
user.userinfo = res
})
})
</script> </script>
<template> <template>
<page-wrapper> <page-wrapper>
<div class="p-4 flex flex-col gap-4"> <div class="p-4 flex flex-col gap-4">
<WdCellGroup :border="true"> <WdCellGroup :border="true">
<WdCell title="用户名" value="test1" /> <WdCell title="用户名" :value="user.userinfo?.username || '-'" />
<WdCell title="单位" value="重庆眩生花科技有限公司" /> <WdCell title="邮箱" :value="user.userinfo?.email || '-'" />
<WdCell title="角色" value="普通管理员" /> <WdCell title="单位" :value="user.userinfo?.department_id || '-'" />
<WdCell title="手机" value="15023333333" /> <WdCell title="角色" :value="user.userinfo?.roles.join('') || '-'" />
</WdCellGroup> </WdCellGroup>
<div class="px-4"> <div class="px-4">
<wd-button plain hairline type="error">退出账号</wd-button> <wd-button plain hairline block type="error" @click="logout">退出账号</wd-button>
</div> </div>
</div> </div>
</page-wrapper> </page-wrapper>
</template> </template>
<style scoped> <style scoped></style>
</style>

17
src/stores/persist.ts Normal file
View File

@ -0,0 +1,17 @@
import type { PiniaPluginContext } from "pinia";
import { deepClone } from "wot-design-uni/components/common/util";
export function persist({ store }: PiniaPluginContext) {
// 暂存State
let persistState = deepClone(store.$state);
// 从缓存中读取
const storageState = uni.getStorageSync(store.$id);
if (storageState) {
persistState = storageState;
}
store.$state = persistState;
store.$subscribe(() => {
// 在存储变化的时候将store缓存
uni.setStorageSync(store.$id, deepClone(store.$state));
});
}

View File

@ -3,9 +3,17 @@ import { defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
export const useUser = defineStore("user", () => { export const useUser = defineStore("user", () => {
const token = ref<string | null>(null);
const userinfo = ref<User | null>(null); const userinfo = ref<User | null>(null);
function logout() {
token.value = null
userinfo.value = null
}
return { return {
token,
userinfo, userinfo,
logout,
}; };
}); });