初始化
This commit is contained in:
57
web/src/stores/adminInfo.ts
Normal file
57
web/src/stores/adminInfo.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ADMIN_INFO } from '/@/stores/constant/cacheKey'
|
||||
import type { AdminInfo } from '/@/stores/interface'
|
||||
|
||||
export const useAdminInfo = defineStore('adminInfo', {
|
||||
state: (): AdminInfo => {
|
||||
return {
|
||||
id: 0,
|
||||
username: '',
|
||||
nickname: '',
|
||||
avatar: '',
|
||||
last_login_time: '',
|
||||
token: '',
|
||||
refresh_token: '',
|
||||
super: false,
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 状态批量填充
|
||||
* @param state 新状态数据
|
||||
* @param [exclude=true] 是否排除某些字段(忽略填充),默认值 true 排除 token 和 refresh_token,传递 false 则不排除,还可传递 string[] 指定排除字段列表
|
||||
*/
|
||||
dataFill(state: Partial<AdminInfo>, exclude: boolean | string[] = true) {
|
||||
if (exclude === true) {
|
||||
exclude = ['token', 'refresh_token']
|
||||
} else if (exclude === false) {
|
||||
exclude = []
|
||||
}
|
||||
|
||||
if (Array.isArray(exclude)) {
|
||||
exclude.forEach((item) => {
|
||||
delete state[item as keyof AdminInfo]
|
||||
})
|
||||
}
|
||||
|
||||
this.$patch(state)
|
||||
},
|
||||
removeToken() {
|
||||
this.token = ''
|
||||
this.refresh_token = ''
|
||||
},
|
||||
setToken(token: string, type: 'auth' | 'refresh') {
|
||||
const field = type == 'auth' ? 'token' : 'refresh_token'
|
||||
this[field] = token
|
||||
},
|
||||
getToken(type: 'auth' | 'refresh' = 'auth') {
|
||||
return type === 'auth' ? this.token : this.refresh_token
|
||||
},
|
||||
setSuper(val: boolean) {
|
||||
this.super = val
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
key: ADMIN_INFO,
|
||||
},
|
||||
})
|
||||
82
web/src/stores/baAccount.ts
Normal file
82
web/src/stores/baAccount.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import router from '../router'
|
||||
import { baAccountLogout } from '/@/api/backend/index'
|
||||
import { BA_ACCOUNT } from '/@/stores/constant/cacheKey'
|
||||
import type { UserInfo } from '/@/stores/interface'
|
||||
import { Local } from '/@/utils/storage'
|
||||
|
||||
export const useBaAccount = defineStore('baAccount', {
|
||||
state: (): Partial<UserInfo> => {
|
||||
return {
|
||||
id: 0,
|
||||
username: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
avatar: '',
|
||||
gender: 0,
|
||||
birthday: '',
|
||||
money: 0,
|
||||
score: 0,
|
||||
motto: '',
|
||||
token: '',
|
||||
refresh_token: '',
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 状态批量填充
|
||||
* @param state 新状态数据
|
||||
* @param [exclude=true] 是否排除某些字段(忽略填充),默认值 true 排除 token 和 refresh_token,传递 false 则不排除,还可传递 string[] 指定排除字段列表
|
||||
*/
|
||||
dataFill(state: Partial<UserInfo>, exclude: boolean | string[] = true) {
|
||||
if (exclude === true) {
|
||||
exclude = ['token', 'refresh_token']
|
||||
} else if (exclude === false) {
|
||||
exclude = []
|
||||
}
|
||||
|
||||
if (Array.isArray(exclude)) {
|
||||
exclude.forEach((item) => {
|
||||
delete state[item as keyof UserInfo]
|
||||
})
|
||||
}
|
||||
|
||||
this.$patch(state)
|
||||
},
|
||||
removeToken() {
|
||||
this.token = ''
|
||||
this.refresh_token = ''
|
||||
},
|
||||
getGenderIcon() {
|
||||
let icon = { name: 'fa fa-transgender-alt', color: 'var(--el-text-color-secondary)' }
|
||||
switch (this.gender) {
|
||||
case 1:
|
||||
icon = { name: 'fa fa-mars-stroke-v', color: 'var(--el-color-primary)' }
|
||||
break
|
||||
case 2:
|
||||
icon = { name: 'fa fa-mars-stroke', color: 'var(--el-color-danger)' }
|
||||
break
|
||||
}
|
||||
return icon
|
||||
},
|
||||
setToken(token: string, type: 'auth' | 'refresh') {
|
||||
const field = type == 'auth' ? 'token' : 'refresh_token'
|
||||
this[field] = token
|
||||
},
|
||||
getToken(type: 'auth' | 'refresh' = 'auth') {
|
||||
return type === 'auth' ? this.token : this.refresh_token
|
||||
},
|
||||
logout() {
|
||||
baAccountLogout().then((res) => {
|
||||
if (res.code == 1) {
|
||||
Local.remove(BA_ACCOUNT)
|
||||
router.go(0)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
key: BA_ACCOUNT,
|
||||
},
|
||||
})
|
||||
111
web/src/stores/config.ts
Normal file
111
web/src/stores/config.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive } from 'vue'
|
||||
import { STORE_CONFIG } from '/@/stores/constant/cacheKey'
|
||||
import type { Crud, Lang, Layout } from '/@/stores/interface'
|
||||
|
||||
export const useConfig = defineStore(
|
||||
'config',
|
||||
() => {
|
||||
const layout: Layout = reactive({
|
||||
// 全局
|
||||
showDrawer: false,
|
||||
shrink: false,
|
||||
layoutMode: 'Default',
|
||||
mainAnimation: 'slide-right',
|
||||
isDark: false,
|
||||
|
||||
// 侧边栏
|
||||
menuBackground: ['#ffffff', '#1d1e1f'],
|
||||
menuColor: ['#303133', '#CFD3DC'],
|
||||
menuActiveBackground: ['#ffffff', '#1d1e1f'],
|
||||
menuActiveColor: ['#409eff', '#3375b9'],
|
||||
menuTopBarBackground: ['#fcfcfc', '#1d1e1f'],
|
||||
menuWidth: 260,
|
||||
menuDefaultIcon: 'fa fa-circle-o',
|
||||
menuCollapse: false,
|
||||
menuUniqueOpened: false,
|
||||
menuShowTopBar: true,
|
||||
|
||||
// 顶栏
|
||||
headerBarTabColor: ['#000000', '#CFD3DC'],
|
||||
headerBarTabActiveBackground: ['#ffffff', '#1d1e1f'],
|
||||
headerBarTabActiveColor: ['#000000', '#409EFF'],
|
||||
headerBarBackground: ['#ffffff', '#1d1e1f'],
|
||||
headerBarHoverBackground: ['#f5f5f5', '#18222c'],
|
||||
})
|
||||
|
||||
const lang: Lang = reactive({
|
||||
defaultLang: 'zh-cn',
|
||||
fallbackLang: 'zh-cn',
|
||||
langArray: [
|
||||
{ name: 'zh-cn', value: '中文简体' },
|
||||
{ name: 'en', value: 'English' },
|
||||
],
|
||||
})
|
||||
|
||||
const crud: Crud = reactive({
|
||||
syncType: 'manual',
|
||||
syncedUpdate: 'yes',
|
||||
syncAutoPublic: 'no',
|
||||
})
|
||||
|
||||
function menuWidth() {
|
||||
if (layout.shrink) {
|
||||
return layout.menuCollapse ? '0px' : layout.menuWidth + 'px'
|
||||
}
|
||||
// 菜单是否折叠
|
||||
return layout.menuCollapse ? '64px' : layout.menuWidth + 'px'
|
||||
}
|
||||
|
||||
function setLang(val: string) {
|
||||
lang.defaultLang = val
|
||||
}
|
||||
|
||||
function onSetLayoutColor(data = layout.layoutMode) {
|
||||
// 切换布局时,如果是为默认配色方案,对菜单激活背景色重新赋值
|
||||
const tempValue = layout.isDark ? { idx: 1, color: '#1d1e1f', newColor: '#141414' } : { idx: 0, color: '#ffffff', newColor: '#f5f5f5' }
|
||||
if (
|
||||
data == 'Classic' &&
|
||||
layout.headerBarBackground[tempValue.idx] == tempValue.color &&
|
||||
layout.headerBarTabActiveBackground[tempValue.idx] == tempValue.color
|
||||
) {
|
||||
layout.headerBarTabActiveBackground[tempValue.idx] = tempValue.newColor
|
||||
} else if (
|
||||
data == 'Default' &&
|
||||
layout.headerBarBackground[tempValue.idx] == tempValue.color &&
|
||||
layout.headerBarTabActiveBackground[tempValue.idx] == tempValue.newColor
|
||||
) {
|
||||
layout.headerBarTabActiveBackground[tempValue.idx] = tempValue.color
|
||||
}
|
||||
}
|
||||
|
||||
function setLayoutMode(data: string) {
|
||||
layout.layoutMode = data
|
||||
onSetLayoutColor(data)
|
||||
}
|
||||
|
||||
const setLayout = (name: keyof Layout, value: any) => {
|
||||
;(layout[name] as any) = value
|
||||
}
|
||||
|
||||
const getColorVal = function (name: keyof Layout): string {
|
||||
const colors = layout[name] as string[]
|
||||
if (layout.isDark) {
|
||||
return colors[1]
|
||||
} else {
|
||||
return colors[0]
|
||||
}
|
||||
}
|
||||
|
||||
const setCrud = (name: keyof Crud, value: any) => {
|
||||
;(crud[name] as any) = value
|
||||
}
|
||||
|
||||
return { layout, lang, crud, menuWidth, setLang, setLayoutMode, setLayout, getColorVal, onSetLayoutColor, setCrud }
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: STORE_CONFIG,
|
||||
},
|
||||
}
|
||||
)
|
||||
25
web/src/stores/constant/cacheKey.ts
Normal file
25
web/src/stores/constant/cacheKey.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 本地缓存Key
|
||||
*/
|
||||
|
||||
// 管理员资料
|
||||
export const ADMIN_INFO = 'adminInfo'
|
||||
|
||||
// WEB端布局配置
|
||||
export const STORE_CONFIG = 'storeConfig_v2'
|
||||
// 后台标签页
|
||||
export const STORE_TAB_VIEW_CONFIG = 'storeTabViewConfig'
|
||||
// 终端
|
||||
export const STORE_TERMINAL = 'storeTerminal'
|
||||
|
||||
// 工作时间
|
||||
export const WORKING_TIME = 'workingTime'
|
||||
|
||||
// 切换到手机端前的上次布局方式
|
||||
export const BEFORE_RESIZE_LAYOUT = 'beforeResizeLayout'
|
||||
|
||||
// 会员资料
|
||||
export const USER_INFO = 'userInfo'
|
||||
|
||||
// ba官网用户信息
|
||||
export const BA_ACCOUNT = 'ba_account'
|
||||
8
web/src/stores/constant/common.ts
Normal file
8
web/src/stores/constant/common.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 公共常量定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 系统级 z-index 配置,比如全局通知消息的 z-index(浏览器支持的最大值通常为 2147483647)
|
||||
*/
|
||||
export const SYSTEM_ZINDEX = 2147483600
|
||||
8
web/src/stores/constant/terminalTaskStatus.ts
Normal file
8
web/src/stores/constant/terminalTaskStatus.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const enum taskStatus {
|
||||
Waiting,
|
||||
Connecting,
|
||||
Executing,
|
||||
Success,
|
||||
Failed,
|
||||
Unknown,
|
||||
}
|
||||
7
web/src/stores/index.ts
Normal file
7
web/src/stores/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
export default pinia
|
||||
203
web/src/stores/interface/index.ts
Normal file
203
web/src/stores/interface/index.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export interface Layout {
|
||||
/* 全局 - s */
|
||||
// 是否显示布局配置抽屉
|
||||
showDrawer: boolean
|
||||
// 是否收缩布局(小屏设备)
|
||||
shrink: boolean
|
||||
// 后台布局方式,可选值<Default|Classic|Streamline|Double>
|
||||
layoutMode: string
|
||||
// 后台主页面切换动画,可选值<slide-right|slide-left|el-fade-in-linear|el-fade-in|el-zoom-in-center|el-zoom-in-top|el-zoom-in-bottom>
|
||||
mainAnimation: string
|
||||
// 是否暗黑模式
|
||||
isDark: boolean
|
||||
/* 全局 - e */
|
||||
|
||||
/* 侧边栏 - s */
|
||||
// 侧边菜单宽度(展开时),单位px
|
||||
menuWidth: number
|
||||
// 侧边菜单项默认图标
|
||||
menuDefaultIcon: string
|
||||
// 是否水平折叠收起菜单
|
||||
menuCollapse: boolean
|
||||
// 是否只保持一个子菜单的展开(手风琴)
|
||||
menuUniqueOpened: boolean
|
||||
// 显示菜单栏顶栏(LOGO)
|
||||
menuShowTopBar: boolean
|
||||
// 侧边菜单背景色
|
||||
menuBackground: string[]
|
||||
// 侧边菜单文字颜色
|
||||
menuColor: string[]
|
||||
// 侧边菜单激活项背景色
|
||||
menuActiveBackground: string[]
|
||||
// 侧边菜单激活项文字色
|
||||
menuActiveColor: string[]
|
||||
// 侧边菜单顶栏背景色
|
||||
menuTopBarBackground: string[]
|
||||
/* 侧边栏 - e */
|
||||
|
||||
/* 顶栏 - s */
|
||||
// 顶栏文字色
|
||||
headerBarTabColor: string[]
|
||||
// 顶栏背景色
|
||||
headerBarBackground: string[]
|
||||
// 顶栏悬停时背景色
|
||||
headerBarHoverBackground: string[]
|
||||
// 顶栏激活项背景色
|
||||
headerBarTabActiveBackground: string[]
|
||||
// 顶栏激活项文字色
|
||||
headerBarTabActiveColor: string[]
|
||||
/* 顶栏 - e */
|
||||
}
|
||||
|
||||
export interface Lang {
|
||||
// 默认语言,可选值<zh-cn|en>
|
||||
defaultLang: string
|
||||
// 当在默认语言包找不到翻译时,继续在 fallbackLang 语言包内查找翻译
|
||||
fallbackLang: string
|
||||
// 支持的语言列表
|
||||
langArray: { name: string; value: string }[]
|
||||
}
|
||||
|
||||
export interface Crud {
|
||||
// 日志同步方式
|
||||
syncType: 'manual' | 'automatic'
|
||||
// 已同步记录被更新时,是否自动重新同步
|
||||
syncedUpdate: 'no' | 'yes'
|
||||
// 自动同步时是否分享至开源社区
|
||||
syncAutoPublic: 'no' | 'yes'
|
||||
}
|
||||
|
||||
export interface NavTabs {
|
||||
// 激活 tab 的 index
|
||||
activeIndex: number
|
||||
// 激活的 tab
|
||||
activeRoute: RouteLocationNormalized | null
|
||||
// tab 列表
|
||||
tabsView: RouteLocationNormalized[]
|
||||
// 当前 tab 是否全屏
|
||||
tabFullScreen: boolean
|
||||
// 从后台加载到的菜单路由列表
|
||||
tabsViewRoutes: RouteRecordRaw[]
|
||||
// 权限节点
|
||||
authNode: Map<string, string[]>
|
||||
}
|
||||
|
||||
export interface MemberCenter {
|
||||
// 是否开启会员中心
|
||||
open: boolean
|
||||
// 布局模式
|
||||
layoutMode: string
|
||||
// 从后台加载到的菜单
|
||||
viewRoutes: RouteRecordRaw[]
|
||||
// 是否显示一级菜单标题(当有多个一级菜单分组时显示)
|
||||
showHeadline: boolean
|
||||
// 权限节点
|
||||
authNode: Map<string, string[]>
|
||||
// 收缩布局(小屏设备)
|
||||
shrink: boolean
|
||||
// 菜单展开状态(小屏设备)
|
||||
menuExpand: boolean
|
||||
// 顶栏会员菜单下拉项
|
||||
navUserMenus: RouteRecordRaw[]
|
||||
}
|
||||
|
||||
export interface AdminInfo {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
avatar: string
|
||||
last_login_time: string
|
||||
token: string
|
||||
refresh_token: string
|
||||
// 是否是 superAdmin,用于判定是否显示终端按钮等,不做任何权限判断
|
||||
super: boolean
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
email: string
|
||||
mobile: string
|
||||
gender: number
|
||||
birthday: string
|
||||
money: number
|
||||
score: number
|
||||
avatar: string
|
||||
last_login_time: string
|
||||
last_login_ip: string
|
||||
join_time: string
|
||||
motto: string
|
||||
token: string
|
||||
refresh_token: string
|
||||
}
|
||||
|
||||
export interface TaskItem {
|
||||
// 任务唯一标识
|
||||
uuid: string
|
||||
// 创建时间
|
||||
createTime: string
|
||||
// 状态
|
||||
status: number
|
||||
// 命令
|
||||
command: string
|
||||
// 命令执行日志
|
||||
message: string[]
|
||||
// 显示命令执行日志
|
||||
showMessage: boolean
|
||||
// 失败阻断后续命令执行
|
||||
blockOnFailure: boolean
|
||||
// 扩展信息,自动发送到后台
|
||||
extend: string
|
||||
// 执行结果回调
|
||||
callback: Function
|
||||
}
|
||||
|
||||
export interface Terminal {
|
||||
// 显示终端窗口
|
||||
show: boolean
|
||||
// 在后台终端按钮上显示一个红点
|
||||
showDot: boolean
|
||||
// 任务列表
|
||||
taskList: TaskItem[]
|
||||
// 包管理器
|
||||
packageManager: string
|
||||
// 显示终端设置窗口
|
||||
showConfig: boolean
|
||||
// 开始任务时自动清理已完成任务
|
||||
automaticCleanupTask: string
|
||||
// PHP 开发服务环境
|
||||
phpDevelopmentServer: boolean
|
||||
// NPM 源
|
||||
npmRegistry: string
|
||||
// composer 源
|
||||
composerRegistry: string
|
||||
}
|
||||
|
||||
export interface SiteConfig {
|
||||
// 站点名称
|
||||
siteName: string
|
||||
// 系统版本号
|
||||
version: string
|
||||
// 内容分发网络URL
|
||||
cdnUrl: string
|
||||
// 中心接口地址(用于请求模块市场的数据等用途)
|
||||
apiUrl: string
|
||||
// 上传配置
|
||||
upload: {
|
||||
mode: string
|
||||
[key: string]: any
|
||||
}
|
||||
// 顶部导航菜单数据
|
||||
headNav: RouteRecordRaw[]
|
||||
// 备案号
|
||||
recordNumber?: string
|
||||
// 内容分发网络URL的参数,格式如 imageMogr2/format/heif
|
||||
cdnUrlParams: string
|
||||
|
||||
// 初始化状态
|
||||
initialize: boolean
|
||||
userInitialize: boolean
|
||||
}
|
||||
84
web/src/stores/memberCenter.ts
Normal file
84
web/src/stores/memberCenter.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive } from 'vue'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { MemberCenter } from '/@/stores/interface/index'
|
||||
|
||||
export const useMemberCenter = defineStore('memberCenter', () => {
|
||||
const state: MemberCenter = reactive({
|
||||
open: true,
|
||||
layoutMode: 'Default',
|
||||
viewRoutes: [],
|
||||
showHeadline: false,
|
||||
authNode: new Map(),
|
||||
shrink: false,
|
||||
menuExpand: false,
|
||||
navUserMenus: [],
|
||||
})
|
||||
|
||||
const setNavUserMenus = (menus: RouteRecordRaw[]) => {
|
||||
state.navUserMenus = menus
|
||||
}
|
||||
|
||||
const mergeNavUserMenus = (menus: RouteRecordRaw[]) => {
|
||||
state.navUserMenus = [...state.navUserMenus, ...menus]
|
||||
}
|
||||
|
||||
const setAuthNode = (key: string, data: string[]) => {
|
||||
state.authNode.set(key, data)
|
||||
}
|
||||
|
||||
const mergeAuthNode = (authNode: Map<string, string[]>) => {
|
||||
state.authNode = new Map([...state.authNode, ...authNode])
|
||||
}
|
||||
|
||||
const setViewRoutes = (data: RouteRecordRaw[]): void => {
|
||||
state.viewRoutes = encodeRoutesURI(data)
|
||||
}
|
||||
|
||||
const setShowHeadline = (show: boolean): void => {
|
||||
state.showHeadline = show
|
||||
}
|
||||
|
||||
const setShrink = (shrink: boolean) => {
|
||||
state.shrink = shrink
|
||||
}
|
||||
|
||||
const setStatus = (status: boolean) => {
|
||||
state.open = status
|
||||
}
|
||||
|
||||
const setLayoutMode = (mode: string) => {
|
||||
state.layoutMode = mode
|
||||
}
|
||||
|
||||
const toggleMenuExpand = (expand = !state.menuExpand) => {
|
||||
state.menuExpand = expand
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
setNavUserMenus,
|
||||
mergeNavUserMenus,
|
||||
setAuthNode,
|
||||
mergeAuthNode,
|
||||
setViewRoutes,
|
||||
setShowHeadline,
|
||||
setShrink,
|
||||
setStatus,
|
||||
setLayoutMode,
|
||||
toggleMenuExpand,
|
||||
}
|
||||
})
|
||||
|
||||
function encodeRoutesURI(data: RouteRecordRaw[]) {
|
||||
data.forEach((item) => {
|
||||
if (item.meta?.menu_type == 'iframe') {
|
||||
item.path = '/user/iframe/' + encodeURIComponent(item.path)
|
||||
}
|
||||
|
||||
if (item.children && item.children.length) {
|
||||
item.children = encodeRoutesURI(item.children)
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
245
web/src/stores/navTabs.ts
Normal file
245
web/src/stores/navTabs.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive } from 'vue'
|
||||
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
|
||||
import { i18n } from '../lang'
|
||||
import { adminBaseRoutePath } from '/@/router/static/adminBase'
|
||||
import { STORE_TAB_VIEW_CONFIG } from '/@/stores/constant/cacheKey'
|
||||
import type { NavTabs } from '/@/stores/interface/index'
|
||||
import { layoutNavTabsRef } from '/@/stores/refs'
|
||||
|
||||
export const useNavTabs = defineStore(
|
||||
'navTabs',
|
||||
() => {
|
||||
const state: NavTabs = reactive({
|
||||
activeIndex: 0,
|
||||
activeRoute: null,
|
||||
tabsView: [],
|
||||
tabFullScreen: false,
|
||||
tabsViewRoutes: [],
|
||||
authNode: new Map(),
|
||||
})
|
||||
|
||||
/**
|
||||
* 通过路由路径关闭tab
|
||||
* @param fullPath 需要关闭的 tab 的路径
|
||||
*/
|
||||
const closeTabByPath = (fullPath: string) => {
|
||||
layoutNavTabsRef.value?.closeTabByPath(fullPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有tab
|
||||
* @param menu 需要保留的标签,否则关闭全部标签并打开第一个路由
|
||||
*/
|
||||
const closeAllTab = (menu?: RouteLocationNormalized) => {
|
||||
layoutNavTabsRef.value?.closeAllTab(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 tab 标题
|
||||
* @param fullPath 需要修改标题的 tab 的路径
|
||||
* @param title 新的标题
|
||||
*/
|
||||
const updateTabTitle = (fullPath: string, title: string) => {
|
||||
layoutNavTabsRef.value?.updateTabTitle(fullPath, title)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 tab(内部)
|
||||
* ps: router.push 时可自动完成 tab 添加,无需调用此方法
|
||||
*/
|
||||
function _addTab(route: RouteLocationNormalized) {
|
||||
const tabView = { ...route, matched: [], meta: { ...route.meta } }
|
||||
if (!tabView.meta.addtab) return
|
||||
|
||||
// 通过路由寻找菜单的原始数据
|
||||
const tabViewRoute = getTabsViewDataByRoute(tabView)
|
||||
if (tabViewRoute && tabViewRoute.meta) {
|
||||
tabView.name = tabViewRoute.name
|
||||
tabView.meta.id = tabViewRoute.meta.id
|
||||
tabView.meta.title = tabViewRoute.meta.title
|
||||
}
|
||||
|
||||
for (const key in state.tabsView) {
|
||||
// 菜单已在 tabs 存在,更新 params 和 query
|
||||
if (state.tabsView[key].meta.id === tabView.meta.id || state.tabsView[key].fullPath == tabView.fullPath) {
|
||||
state.tabsView[key].fullPath = tabView.fullPath
|
||||
state.tabsView[key].params = !isEmpty(tabView.params) ? tabView.params : state.tabsView[key].params
|
||||
state.tabsView[key].query = !isEmpty(tabView.query) ? tabView.query : state.tabsView[key].query
|
||||
return
|
||||
}
|
||||
}
|
||||
if (typeof tabView.meta.title == 'string') {
|
||||
tabView.meta.title = i18n.global.te(tabView.meta.title) ? i18n.global.t(tabView.meta.title) : tabView.meta.title
|
||||
}
|
||||
state.tabsView.push(tabView)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置激活 tab(内部)
|
||||
* ps: router.push 时可自动完成 tab 激活,无需调用此方法
|
||||
*/
|
||||
const _setActiveRoute = (route: RouteLocationNormalized): void => {
|
||||
const currentRouteIndex: number = state.tabsView.findIndex((item: RouteLocationNormalized) => {
|
||||
return item.fullPath === route.fullPath
|
||||
})
|
||||
if (currentRouteIndex === -1) return
|
||||
state.activeRoute = route
|
||||
state.activeIndex = currentRouteIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭 tab(内部)
|
||||
* ps: 使用 closeTabByPath 代替
|
||||
*/
|
||||
function _closeTab(route: RouteLocationNormalized) {
|
||||
state.tabsView.map((v, k) => {
|
||||
if (v.fullPath == route.fullPath) {
|
||||
state.tabsView.splice(k, 1)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭多个标签(内部)
|
||||
* ps:使用 closeAllTab 代替
|
||||
*/
|
||||
const _closeTabs = (retainMenu: RouteLocationNormalized | false = false) => {
|
||||
if (retainMenu) {
|
||||
state.tabsView = [retainMenu]
|
||||
} else {
|
||||
state.tabsView = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新标签标题(内部)
|
||||
* ps: 使用 updateTabTitle 代替
|
||||
*/
|
||||
const _updateTabTitle = (fullPath: string, title: string) => {
|
||||
for (const key in state.tabsView) {
|
||||
if (state.tabsView[key].fullPath == fullPath) {
|
||||
state.tabsView[key].meta.title = title
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置从后台加载到的菜单路由列表
|
||||
*/
|
||||
const setTabsViewRoutes = (data: RouteRecordRaw[]): void => {
|
||||
state.tabsViewRoutes = encodeRoutesURI(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 以key设置权限节点
|
||||
*/
|
||||
const setAuthNode = (key: string, data: string[]) => {
|
||||
state.authNode.set(key, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖设置权限节点
|
||||
*/
|
||||
const fillAuthNode = (data: Map<string, string[]>) => {
|
||||
state.authNode = data
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前 tab 是否全屏
|
||||
* @param status 全屏状态
|
||||
*/
|
||||
const setFullScreen = (status: boolean): void => {
|
||||
state.tabFullScreen = status
|
||||
}
|
||||
|
||||
/**
|
||||
* 寻找路由在菜单中的数据
|
||||
* @param route 路由
|
||||
* @param returnType 返回值要求:normal=返回被搜索的路径对应的菜单数据,above=返回被搜索的路径对应的上一级菜单数组
|
||||
*/
|
||||
const getTabsViewDataByRoute = (route: RouteLocationNormalized, returnType: 'normal' | 'above' = 'normal'): RouteRecordRaw | false => {
|
||||
// 以完整路径寻找
|
||||
let found = getTabsViewDataByPath(route.fullPath, state.tabsViewRoutes, returnType)
|
||||
if (found) {
|
||||
found.meta!.matched = route.fullPath
|
||||
return found
|
||||
}
|
||||
|
||||
// 以路径寻找
|
||||
found = getTabsViewDataByPath(route.path, state.tabsViewRoutes, returnType)
|
||||
if (found) {
|
||||
found.meta!.matched = route.path
|
||||
return found
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归的寻找路由路径在菜单中的数据
|
||||
* @param path 路由路径
|
||||
* @param menus 菜单数据(只有 path 代表完整 url,没有 fullPath)
|
||||
* @param returnType 返回值要求:normal=返回被搜索的路径对应的菜单数据,above=返回被搜索的路径对应的上一级菜单数组
|
||||
*/
|
||||
const getTabsViewDataByPath = (path: string, menus: RouteRecordRaw[], returnType: 'normal' | 'above'): RouteRecordRaw | false => {
|
||||
for (const key in menus) {
|
||||
// 找到目标
|
||||
if (menus[key].path === path) {
|
||||
return menus[key]
|
||||
}
|
||||
// 从子级继续寻找
|
||||
if (menus[key].children && menus[key].children.length) {
|
||||
const find = getTabsViewDataByPath(path, menus[key].children, returnType)
|
||||
if (find) {
|
||||
return returnType == 'above' ? menus[key] : find
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
closeAllTab,
|
||||
closeTabByPath,
|
||||
updateTabTitle,
|
||||
setTabsViewRoutes,
|
||||
setAuthNode,
|
||||
fillAuthNode,
|
||||
setFullScreen,
|
||||
getTabsViewDataByPath,
|
||||
getTabsViewDataByRoute,
|
||||
_addTab,
|
||||
_closeTab,
|
||||
_closeTabs,
|
||||
_setActiveRoute,
|
||||
_updateTabTitle,
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: STORE_TAB_VIEW_CONFIG,
|
||||
pick: ['state.tabFullScreen'],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 对iframe的url进行编码
|
||||
*/
|
||||
function encodeRoutesURI(data: RouteRecordRaw[]) {
|
||||
data.forEach((item) => {
|
||||
if (item.meta?.menu_type == 'iframe') {
|
||||
item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path)
|
||||
}
|
||||
|
||||
if (item.children && item.children.length) {
|
||||
item.children = encodeRoutesURI(item.children)
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
34
web/src/stores/refs.ts
Normal file
34
web/src/stores/refs.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* references
|
||||
* 全局提供:引用(指向)一些对象(组件)的句柄
|
||||
*/
|
||||
import type { ScrollbarInstance } from 'element-plus'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import NavTabs from '/@/layouts/backend/components/navBar/tabs.vue'
|
||||
import { mainHeight } from '/@/utils/layout'
|
||||
|
||||
/**
|
||||
* 后台顶栏(tabs)组件ref(仅默认和经典布局)
|
||||
*/
|
||||
export const layoutNavTabsRef = ref<InstanceType<typeof NavTabs>>()
|
||||
|
||||
/**
|
||||
* 前后台布局的主体的滚动条组件ref
|
||||
*/
|
||||
export const layoutMainScrollbarRef = ref<ScrollbarInstance>()
|
||||
|
||||
/**
|
||||
* 前后台布局的主体滚动条的额外样式,包括高度
|
||||
*/
|
||||
export const layoutMainScrollbarStyle = computed<CSSProperties>(() => mainHeight())
|
||||
|
||||
/**
|
||||
* 前后台布局的菜单组件ref
|
||||
*/
|
||||
export const layoutMenuRef = ref<ScrollbarInstance>()
|
||||
|
||||
/**
|
||||
* 前后台布局的菜单栏滚动条组件ref
|
||||
*/
|
||||
export const layoutMenuScrollbarRef = ref<ScrollbarInstance>()
|
||||
37
web/src/stores/siteConfig.ts
Normal file
37
web/src/stores/siteConfig.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { SiteConfig } from '/@/stores/interface'
|
||||
|
||||
export const useSiteConfig = defineStore('siteConfig', {
|
||||
state: (): SiteConfig => {
|
||||
return {
|
||||
siteName: '',
|
||||
version: '',
|
||||
cdnUrl: '',
|
||||
apiUrl: '',
|
||||
upload: {
|
||||
mode: 'local',
|
||||
},
|
||||
headNav: [],
|
||||
recordNumber: '',
|
||||
cdnUrlParams: '',
|
||||
initialize: false,
|
||||
userInitialize: false,
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
dataFill(state: SiteConfig) {
|
||||
// 使用 this.$patch(state) 时 headNav 的类型异常,直接赋值
|
||||
this.$state = state
|
||||
},
|
||||
setHeadNav(headNav: RouteRecordRaw[]) {
|
||||
this.headNav = headNav
|
||||
},
|
||||
setInitialize(initialize: boolean) {
|
||||
this.initialize = initialize
|
||||
},
|
||||
setUserInitialize(userInitialize: boolean) {
|
||||
this.userInitialize = userInitialize
|
||||
},
|
||||
},
|
||||
})
|
||||
292
web/src/stores/terminal.ts
Normal file
292
web/src/stores/terminal.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { defineStore } from 'pinia'
|
||||
import { nextTick, reactive } from 'vue'
|
||||
import { buildTerminalUrl } from '/@/api/common'
|
||||
import { i18n } from '/@/lang/index'
|
||||
import { STORE_TERMINAL } from '/@/stores/constant/cacheKey'
|
||||
import { SYSTEM_ZINDEX } from '/@/stores/constant/common'
|
||||
import { taskStatus } from '/@/stores/constant/terminalTaskStatus'
|
||||
import type { Terminal } from '/@/stores/interface/index'
|
||||
import { timeFormat } from '/@/utils/common'
|
||||
import { uuid } from '/@/utils/random'
|
||||
import { closeHotUpdate, openHotUpdate } from '/@/utils/vite'
|
||||
|
||||
export const useTerminal = defineStore(
|
||||
'terminal',
|
||||
() => {
|
||||
const state: Terminal = reactive({
|
||||
show: false,
|
||||
showDot: false,
|
||||
taskList: [],
|
||||
packageManager: 'pnpm',
|
||||
showConfig: false,
|
||||
automaticCleanupTask: '1',
|
||||
phpDevelopmentServer: false,
|
||||
npmRegistry: 'unknown',
|
||||
composerRegistry: 'unknown',
|
||||
})
|
||||
|
||||
function init() {
|
||||
for (const key in state.taskList) {
|
||||
if (state.taskList[key].status == taskStatus.Connecting || state.taskList[key].status == taskStatus.Executing) {
|
||||
state.taskList[key].status = taskStatus.Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(val = !state.show) {
|
||||
state.show = val
|
||||
if (val) {
|
||||
toggleDot(false)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDot(val = !state.showDot) {
|
||||
state.showDot = val
|
||||
}
|
||||
|
||||
function toggleConfigDialog(val = !state.showConfig) {
|
||||
toggle(!val)
|
||||
state.showConfig = val
|
||||
}
|
||||
|
||||
function changeRegistry(val: string, type: 'npm' | 'composer') {
|
||||
state[type == 'npm' ? 'npmRegistry' : 'composerRegistry'] = val
|
||||
}
|
||||
|
||||
function changePackageManager(val: string) {
|
||||
state.packageManager = val
|
||||
}
|
||||
|
||||
function changePHPDevelopmentServer(val: boolean) {
|
||||
state.phpDevelopmentServer = val
|
||||
}
|
||||
|
||||
function changeAutomaticCleanupTask(val: '0' | '1') {
|
||||
state.automaticCleanupTask = val
|
||||
}
|
||||
|
||||
function setTaskStatus(idx: number, status: number) {
|
||||
state.taskList[idx].status = status
|
||||
if ((status == taskStatus.Failed || status == taskStatus.Unknown) && state.taskList[idx].blockOnFailure) {
|
||||
setTaskShowMessage(idx, true)
|
||||
}
|
||||
}
|
||||
|
||||
function taskCompleted(idx: number) {
|
||||
// 命令执行完毕,重新打开热更新
|
||||
openHotUpdate('terminal')
|
||||
|
||||
if (typeof state.taskList[idx].callback != 'function') return
|
||||
|
||||
const status = state.taskList[idx].status
|
||||
if (status == taskStatus.Failed || status == taskStatus.Unknown) {
|
||||
state.taskList[idx].callback(taskStatus.Failed)
|
||||
} else if (status == taskStatus.Success) {
|
||||
state.taskList[idx].callback(taskStatus.Success)
|
||||
}
|
||||
}
|
||||
|
||||
function setTaskShowMessage(idx: number, val = !state.taskList[idx].showMessage) {
|
||||
state.taskList[idx].showMessage = val
|
||||
}
|
||||
|
||||
function addTaskMessage(idx: number, message: string) {
|
||||
if (!state.show) toggleDot(true)
|
||||
state.taskList[idx].message = state.taskList[idx].message.concat(message)
|
||||
nextTick(() => {
|
||||
execMessageScrollbarKeep(state.taskList[idx].uuid)
|
||||
})
|
||||
}
|
||||
|
||||
function addTask(command: string, blockOnFailure = true, extend = '', callback: Function = () => {}) {
|
||||
if (!state.show) toggleDot(true)
|
||||
state.taskList = state.taskList.concat({
|
||||
uuid: uuid(),
|
||||
createTime: timeFormat(),
|
||||
status: taskStatus.Waiting,
|
||||
command: command,
|
||||
message: [],
|
||||
showMessage: false,
|
||||
blockOnFailure: blockOnFailure,
|
||||
extend: extend,
|
||||
callback: callback,
|
||||
})
|
||||
|
||||
// 清理任务列表
|
||||
if (parseInt(state.automaticCleanupTask) === 1) {
|
||||
clearSuccessTask()
|
||||
}
|
||||
|
||||
// 检查是否有已经失败的任务
|
||||
if (state.show === false) {
|
||||
for (const key in state.taskList) {
|
||||
if (state.taskList[key].status == taskStatus.Failed || state.taskList[key].status == taskStatus.Unknown) {
|
||||
ElNotification({
|
||||
type: 'error',
|
||||
message: i18n.global.t('terminal.Newly added tasks will never start because they are blocked by failed tasks'),
|
||||
zIndex: SYSTEM_ZINDEX,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startTask()
|
||||
}
|
||||
|
||||
function addTaskPM(command: string, blockOnFailure = true, extend = '', callback: Function = () => {}) {
|
||||
addTask(command + '.' + state.packageManager, blockOnFailure, extend, callback)
|
||||
}
|
||||
|
||||
function delTask(idx: number) {
|
||||
if (state.taskList[idx].status != taskStatus.Connecting && state.taskList[idx].status != taskStatus.Executing) {
|
||||
state.taskList.splice(idx, 1)
|
||||
}
|
||||
startTask()
|
||||
}
|
||||
|
||||
function startTask() {
|
||||
let taskKey = null
|
||||
|
||||
// 寻找可以开始执行的命令
|
||||
for (const key in state.taskList) {
|
||||
if (state.taskList[key].status == taskStatus.Waiting) {
|
||||
taskKey = parseInt(key)
|
||||
break
|
||||
}
|
||||
if (state.taskList[key].status == taskStatus.Connecting || state.taskList[key].status == taskStatus.Executing) {
|
||||
break
|
||||
}
|
||||
if (state.taskList[key].status == taskStatus.Success) {
|
||||
continue
|
||||
}
|
||||
if (state.taskList[key].status == taskStatus.Failed || state.taskList[key].status == taskStatus.Unknown) {
|
||||
if (state.taskList[key].blockOnFailure) {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if (taskKey !== null) {
|
||||
setTaskStatus(taskKey, taskStatus.Connecting)
|
||||
startEventSource(taskKey)
|
||||
}
|
||||
}
|
||||
|
||||
function startEventSource(taskKey: number) {
|
||||
// 命令执行期间禁用热更新
|
||||
closeHotUpdate('terminal')
|
||||
|
||||
window.eventSource = new EventSource(
|
||||
buildTerminalUrl(state.taskList[taskKey].command, state.taskList[taskKey].uuid, state.taskList[taskKey].extend)
|
||||
)
|
||||
window.eventSource.onmessage = function (e) {
|
||||
const data = JSON.parse(e.data)
|
||||
if (!data || !data.data) {
|
||||
return
|
||||
}
|
||||
|
||||
const taskIdx = findTaskIdxFromUuid(data.uuid)
|
||||
if (taskIdx === false) {
|
||||
return
|
||||
}
|
||||
|
||||
if (data.data == 'command-exec-error') {
|
||||
setTaskStatus(taskIdx, taskStatus.Failed)
|
||||
window.eventSource.close()
|
||||
taskCompleted(taskIdx)
|
||||
startTask()
|
||||
} else if (data.data == 'command-exec-completed') {
|
||||
window.eventSource.close()
|
||||
if (state.taskList[taskIdx].status != taskStatus.Success) {
|
||||
setTaskStatus(taskIdx, taskStatus.Failed)
|
||||
}
|
||||
taskCompleted(taskIdx)
|
||||
startTask()
|
||||
} else if (data.data == 'command-link-success') {
|
||||
setTaskStatus(taskIdx, taskStatus.Executing)
|
||||
} else if (data.data == 'command-exec-success') {
|
||||
setTaskStatus(taskIdx, taskStatus.Success)
|
||||
} else {
|
||||
addTaskMessage(taskIdx, data.data)
|
||||
}
|
||||
}
|
||||
window.eventSource.onerror = function () {
|
||||
window.eventSource.close()
|
||||
const taskIdx = findTaskIdxFromGuess(taskKey)
|
||||
if (taskIdx === false) return
|
||||
setTaskStatus(taskIdx, taskStatus.Failed)
|
||||
taskCompleted(taskIdx)
|
||||
}
|
||||
}
|
||||
|
||||
function retryTask(idx: number) {
|
||||
state.taskList[idx].message = []
|
||||
setTaskStatus(idx, taskStatus.Waiting)
|
||||
startTask()
|
||||
}
|
||||
|
||||
function clearSuccessTask() {
|
||||
state.taskList = state.taskList.filter((item) => item.status != taskStatus.Success)
|
||||
}
|
||||
|
||||
function findTaskIdxFromUuid(uuid: string) {
|
||||
for (const key in state.taskList) {
|
||||
if (state.taskList[key].uuid == uuid) {
|
||||
return parseInt(key)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function findTaskIdxFromGuess(idx: number) {
|
||||
if (!state.taskList[idx]) {
|
||||
let taskKey = -1
|
||||
for (const key in state.taskList) {
|
||||
if (state.taskList[key].status == taskStatus.Connecting || state.taskList[key].status == taskStatus.Executing) {
|
||||
taskKey = parseInt(key)
|
||||
}
|
||||
}
|
||||
return taskKey === -1 ? false : taskKey
|
||||
} else {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
|
||||
function execMessageScrollbarKeep(uuid: string) {
|
||||
const execMessageEl = document.querySelector('.exec-message-' + uuid) as Element
|
||||
if (execMessageEl && execMessageEl.scrollHeight) {
|
||||
execMessageEl.scrollTop = execMessageEl.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
init,
|
||||
toggle,
|
||||
toggleDot,
|
||||
setTaskStatus,
|
||||
setTaskShowMessage,
|
||||
addTaskMessage,
|
||||
addTask,
|
||||
addTaskPM,
|
||||
delTask,
|
||||
startTask,
|
||||
retryTask,
|
||||
clearSuccessTask,
|
||||
toggleConfigDialog,
|
||||
changeRegistry,
|
||||
changePackageManager,
|
||||
changePHPDevelopmentServer,
|
||||
changeAutomaticCleanupTask,
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: STORE_TERMINAL,
|
||||
pick: ['state.showDot', 'state.taskList', 'state.automaticCleanupTask', 'state.npmRegistry', 'state.composerRegistry'],
|
||||
},
|
||||
}
|
||||
)
|
||||
88
web/src/stores/userInfo.ts
Normal file
88
web/src/stores/userInfo.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import router from '../router'
|
||||
import { postLogout } from '/@/api/frontend/user/index'
|
||||
import { USER_INFO } from '/@/stores/constant/cacheKey'
|
||||
import type { UserInfo } from '/@/stores/interface'
|
||||
import { Local } from '/@/utils/storage'
|
||||
|
||||
export const useUserInfo = defineStore('userInfo', {
|
||||
state: (): UserInfo => {
|
||||
return {
|
||||
id: 0,
|
||||
username: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
avatar: '',
|
||||
gender: 0,
|
||||
birthday: '',
|
||||
money: 0,
|
||||
score: 0,
|
||||
last_login_time: '',
|
||||
last_login_ip: '',
|
||||
join_time: '',
|
||||
motto: '',
|
||||
token: '',
|
||||
refresh_token: '',
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 状态批量填充
|
||||
* @param state 新状态数据
|
||||
* @param [exclude=true] 是否排除某些字段(忽略填充),默认值 true 排除 token 和 refresh_token,传递 false 则不排除,还可传递 string[] 指定排除字段列表
|
||||
*/
|
||||
dataFill(state: Partial<UserInfo>, exclude: boolean | string[] = true) {
|
||||
if (exclude === true) {
|
||||
exclude = ['token', 'refresh_token']
|
||||
} else if (exclude === false) {
|
||||
exclude = []
|
||||
}
|
||||
|
||||
if (Array.isArray(exclude)) {
|
||||
exclude.forEach((item) => {
|
||||
delete state[item as keyof UserInfo]
|
||||
})
|
||||
}
|
||||
|
||||
this.$patch(state)
|
||||
},
|
||||
removeToken() {
|
||||
this.token = ''
|
||||
this.refresh_token = ''
|
||||
},
|
||||
setToken(token: string, type: 'auth' | 'refresh') {
|
||||
const field = type == 'auth' ? 'token' : 'refresh_token'
|
||||
this[field] = token
|
||||
},
|
||||
getToken(type: 'auth' | 'refresh' = 'auth') {
|
||||
return type === 'auth' ? this.token : this.refresh_token
|
||||
},
|
||||
getGenderIcon() {
|
||||
let icon = { name: 'fa fa-transgender-alt', color: 'var(--el-text-color-secondary)' }
|
||||
switch (this.gender) {
|
||||
case 1:
|
||||
icon = { name: 'fa fa-mars-stroke-v', color: 'var(--el-color-primary)' }
|
||||
break
|
||||
case 2:
|
||||
icon = { name: 'fa fa-mars-stroke', color: 'var(--el-color-danger)' }
|
||||
break
|
||||
}
|
||||
return icon
|
||||
},
|
||||
logout() {
|
||||
postLogout().then((res) => {
|
||||
if (res.code == 1) {
|
||||
Local.remove(USER_INFO)
|
||||
router.go(0)
|
||||
}
|
||||
})
|
||||
},
|
||||
isLogin() {
|
||||
return this.id && this.token
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
key: USER_INFO,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user