├── src
├── api
├── assets
├── components
├── directive
├── layout
├── router
├── pinia
├── modules
├── app.js
├── permission.js
├── settings.js
├── tagsViews.js
├── defineStore.js
├── index.js
├── utils
├── views
├── App.vue
├── main.js
├── permission.js
└── settings.js
npm install @vue/composition-api pinia pinia-plugin-persistedstate -S
pinia持久化插件 ‘pinia-plugin-persistedstate’
Vue2 版本需要用到这个 PiniaVuePlugin
Vue2版本使用pinia需要安装@vue/composition-api
/** main.js */
import Vue from "vue";
import App from "@/App.vue";
Vue.config.productionTip = false
import router from "@/router";
import pinia from "@/pinia";
import { PiniaVuePlugin, setMapStoreSuffix } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
pinia.use(piniaPluginPersistedstate);
setMapStoreSuffix('_pinia');
Vue.use(PiniaVuePlugin);
import ElementUI from "element-ui"
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI);
import "@/assets/icons";
import "@/assets/icons";
import "@/assets/css/global.scss";
import "@/assets/css/common.scss";
import '@/permission';
new Vue({
router,
pinia,
render: (h) => h(App),
}).$mount("#app");
- pinia的模块封装,有的位置需要用到构建后的pinia,在vue2中最常见就是这个createPinia这个报错提示,这个解决犯法还是github中pinia issues 中找到解决方式。
- 还有一个目的就是为什么要二次封装一次,这个目的就是省去创建···createPinia···这个步骤。
/** pinia/index.js */
import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;
export { default as settings } from './modules/settings';
export { default as app } from './modules/app';
export { default as permission } from './modules/permission';
export { default as user } from './modules/user';
export { default as tagsViews } from './modules/tagsViews';
/** defineStore.js */
import { defineStore as useStore } from 'pinia'
import pinia from '@/pinia';
/** 二次改造 defineStore */
export const defineStore = (...args) => {
var store = useStore(...args);
return (p = pinia) => store(p);
}
id:唯一Key、重复定义会失效或覆盖。
state:状态库、必须是函数,否则会报错。
actions:事件逻辑区,可以通过this访问state数据,也可以使用this.$path({})设置响应式状态,重要是支持异步,async、await关键字,都能被vue-devTool获取到改变。
getters:计算属性,用于需要二次加工的属性。
persist:安装pinia持久化插件支持,当然vuex也有相应的插件,有三个主要配置属性
- key:用于存取本地存储的key值,模块相同数据会被覆盖。
- storage:存储方式,默认为localStorage
- paths:存储的属性,以数组作为集合,不支持函数形式。
/** pinia/modules/user.js */
import { defineStore } from '../defineStore'
import { getInfo, login, logout } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth';
export default defineStore({
id: "user",
state() {
return {
token: getToken(),
name: '',
avatar: '',
roles: [],
permissions: [],
}
},
actions: {
getInfo() {
return new Promise((resolve, reject) => {
(async () => {
try {
const res = await getInfo();
const user = res.user
const avatar = user.avatar == "" ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
if (res.roles && res.roles.length > 0) {
this.roles = res.roles
this.permissions = res.permissions
} else {
this.roles = ['ROLE_DEFAULT']
}
this.name = user.userName;
this.avatar = avatar;
resolve(res)
} catch (error) {
reject(error)
}
})();
})
},
login(userInfo) {
return new Promise((resolve, reject) => {
(async () => {
try {
const { token } = await login(userInfo);
setToken(token);
this.token = token
resolve();
} catch (error) {
reject(error);
}
})();
});
},
logOut() {
return new Promise((resolve, reject) => {
try {
logout(this.token);
this.token = '';
this.roles = [];
this.permissions = [];
removeToken()
resolve();
} catch (error) {
reject(error);
}
})
},
},
getters: {},
persist: {
key: 'user-key',
storage: window.sessionStorage,
paths: ['token']
},
});
这里演示如何在组件外如何去使用pinia
- import { settings, permission, user } from '@/pinia';。
- 以此创建即可 const settingsStore = settings(); 不做这个步骤pinia会报一个 setting 没有 install。
- settingsStore.setTitle(to.meta.title); 这个store表示的就是文件实例,你可以任意去使用里面的方法了。
- permission().setTitle(to.meta.title) 这样写也是行的。有时候会出现useStore的错误,这个报错不是很明确,不做多解释。
/** src/permission.js */
import router from './router';
import { settings, permission, user } from '@/pinia';
import { Message } from 'element-ui';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { getToken } from '@/utils/auth';
const whiteList = ['/login', '/auth-redirect', '/bind', '/register'];
const settingsStore = settings();
const permissionStore = permission();
const userStore = user();
NProgress.configure({ showSpinner: true });
router.beforeEach((to, from, next) => {
NProgress.start();
if (getToken()) {
hasToken(to, from, next);
} else {
voidToken(to, from, next);
}
});
router.afterEach(NProgress.done);
async function hasToken(to, from, next) {
to.meta.title && settingsStore.setTitle(to.meta.title);
if (to.path === '/login') return next({ path: '/' }), NProgress.done();
if (userStore.roles.length !== 0) return next();
try {
await userStore.getInfo();
const accessRoutes = await permissionStore.GenerateRoutes();
accessRoutes.forEach(item => router.addRoute(item));
next({ ...to, replace: true });
} catch (err) {
console.error("ERROR", err);
await userStore.logOut();
Message.error(err || 'Has Error');
next({ path: '/' });
}
}
async function voidToken(to, from, next) {
if (whiteList.indexOf(to.path) !== -1) return next();
next(`/login?redirect=${to.fullPath}`);
NProgress.done();
}
这里演示组件内如何去使用pinia
mapAction:第一个参数为实例的对象,第二个是其中的方法,多个模块依次导入即可
this.$pinia 也是可以访问到pinia对象。
/** src/layout/components/Navbar/index.vue */
<script>
import { mapState, mapActions } from 'pinia';
import { app, user, settings } from '@/pinia';
import Hamburger from './Hamburger';
import Breadcrumb from './Breadcrumb';
import TopNav from './TopNav';
import Search from './Search';
import Screenfull from './Screenfull';
export default {
name: 'Navbar',
components: { Hamburger, Breadcrumb, TopNav, Search, Screenfull },
methods: {
...mapActions(user, ['logOut']),
...mapActions(settings, ['changeSetting']),
...mapActions(app, ['toggleSideBar']),
async logout() {
const confirmText = await this.$confirm('确定注销并退出系统吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).catch(err => err);
if (confirmText !== 'confirm') return;
try {
await this.logOut();
location.href = '/';
} catch (error) {
this.$message.error(error.message);
}
}
},
computed: {
...mapState(app, ['sidebar', 'device']),
...mapState(user, ['avatar', 'topNav']),
...mapState(settings, ['showSettings']),
setting: {
get() {
return this.showSettings;
},
set(value) {
this.changeSetting({ key: 'showSettings', value });
}
},
}
// End
}
storeToRefs 这个方法是pinia解构出来的,这个使用是例子是官网的代码。
- 传递一个defineStore实例切调用,返回值是vue3的ref响应式对象,需要使用xxx.value.properties拿到属性。
- 可以在组件中直接修改这个响应式.value的属性,这个也能被vue-dev-tool监测到变化,无需配合actions函数。
- 可以在vue2中体会到vue3的composition-api。
/** src/views/system/menuManagement/index.vue */
<template>
<el-row class="mt-95" :gutter="20">
<el-col class="" :span="8" :push="8">
<el-switch v-model="value" :before-change="beforeChange" :loading="loading" activeValue="yes" inactiveValue="no" active-color="#13ce66" width="80" active-text="按月付费" inactive-text="按年付费" />
</el-col>
</el-row>
</template>
<script>
import { mapState, mapActions, storeToRefs } from 'pinia'
import { useCounterStore } from '@/pinia'
export default {
name: 'menuManagement',
data() {
return {
value: 'no',
loading: false,
};
},
methods: {
...mapActions(useCounterStore, ['increment']),
beforeChange() {
this.loading = true;
return new Promise((resolve) => {
setTimeout(() => {
this.$message.success('switch success');
resolve()
this.loading = false;
const { count } = storeToRefs(useCounterStore());
console.log(count);
this.increment();
count.value = 100
}, 3000)
})
}
},
computed: {
...mapState(useCounterStore, {
counter: state => state.count
})
},
// End
}
</script>
<style lang='css' scoped>
</style>
案例连接: 将vuex替换pinia