init: Initialize the basic project.

This commit is contained in:
2026-01-06 09:58:29 +08:00
commit 1ab79d6f8f
1441 changed files with 129326 additions and 0 deletions

174
nl-vue/src/store/global.js Normal file
View File

@@ -0,0 +1,174 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { defineStore } from 'pinia'
import { changeColor } from '@/utils/themeUtil'
import config from '@/config'
import { message } from 'ant-design-vue'
import tool from '@/utils/tool'
const toolDataGet = (key) => {
return tool.data.get(key)
}
// 获取缓存中的,如果取不到那就用配置的
const getCacheConfig = (value) => {
const data = toolDataGet(value)
if (data === null) {
return config[value]
}
return data
}
// deprecated 请使用 useGlobalStore
export const globalStore = defineStore('global', () => {
// 移动端布局
const isMobile = ref(false)
// 布局
const layout = ref(getCacheConfig('SNOWY_LAYOUT'))
// 菜单是否折叠 toggle
const menuIsCollapse = ref(getCacheConfig('SNOWY_MENU_COLLAPSE'))
// 侧边菜单是否排他展开
const sideUniqueOpen = ref(getCacheConfig('SNOWY_SIDE_UNIQUE_OPEN'))
// 多标签栏
const layoutTagsOpen = ref(getCacheConfig('SNOWY_LAYOUT_TAGS_OPEN'))
// 是否展示面包屑
const breadcrumbOpen = ref(getCacheConfig('SNOWY_BREADCRUMB_OPEN'))
// 是否开启固定宽度(顶栏菜单)
const fixedWidth = ref(getCacheConfig('SNOWY_FIXEDWIDTH_OPEN'))
// 顶栏是否应用主题色
const topHeaderThemeColorOpen = ref(getCacheConfig('SNOWY_TOP_HEADER_THEME_COLOR_OPEN'))
// 顶栏主题色通栏
const topHeaderThemeColorSpread = ref(getCacheConfig('SNOWY_TOP_HEADER_THEME_COLOR_SPREAD'))
// 登录用户水印
const loginUserWatermarkOpen = ref(getCacheConfig('SNOWY_LOGIN_USER_WATERMARK_OPEN'))
// 页脚版权信息
const footerCopyrightOpen = ref(getCacheConfig('SNOWY_FOOTER_COPYRIGHT_OPEN'))
// 模块坞
const moduleUnfoldOpen = ref(getCacheConfig('SNOWY_MODULE_UNFOLD_OPEN'))
// 主题
const theme = ref(getCacheConfig('SNOWY_THEME'))
// 主题颜色
const themeColor = ref(toolDataGet('SNOWY_THEME_COLOR') || config.COLOR)
// 圆角分格
const roundedCornerStyleOpen = ref(getCacheConfig('SNOWY_ROUNDED_CORNER_STYLE_OPEN'))
// 整体表单风格
const formStyle = ref(getCacheConfig('SNOWY_FORM_STYLE'))
// 用户信息
const userInfo = ref(toolDataGet('USER_INFO') || {})
// 系统配置
const sysBaseConfig = ref(toolDataGet('SNOWY_SYS_BASE_CONFIG') || config.SYS_BASE_CONFIG)
// 默认应用
const module = ref(getCacheConfig('SNOWY_MENU_MODULE_ID'))
// 定义action
const setIsMobile = (key) => {
isMobile.value = key
}
const setLayout = (key) => {
layout.value = key
}
const setTheme = (key) => {
theme.value = key
changeColor(themeColor.value, key).then()
}
const setThemeColor = (key) => {
themeColor.value = key
changeColor(key, theme.value).then()
}
const initTheme = () => {
changeColor(themeColor.value, theme.value).then()
}
const toggleConfig = (key) => {
switch (key) {
case 'menuIsCollapse':
menuIsCollapse.value = !menuIsCollapse.value
break
case 'topHeaderThemeColorSpread':
topHeaderThemeColorSpread.value = !topHeaderThemeColorSpread.value
break
case 'sideUniqueOpen':
sideUniqueOpen.value = !sideUniqueOpen.value
break
case 'layoutTagsOpen':
layoutTagsOpen.value = !layoutTagsOpen.value
break
case 'breadcrumbOpen':
breadcrumbOpen.value = !breadcrumbOpen.value
break
case 'fixedWidth':
fixedWidth.value = !fixedWidth.value
break
case 'topHeaderThemeColorOpen':
topHeaderThemeColorOpen.value = !topHeaderThemeColorOpen.value
topHeaderThemeColorSpread.value = topHeaderThemeColorOpen.value
? topHeaderThemeColorSpread.value
: topHeaderThemeColorOpen.value
break
case 'loginUserWatermarkOpen':
loginUserWatermarkOpen.value = !loginUserWatermarkOpen.value
break
case 'footerCopyrightOpen':
footerCopyrightOpen.value = !footerCopyrightOpen.value
break
case 'roundedCornerStyleOpen':
roundedCornerStyleOpen.value = !roundedCornerStyleOpen.value
break
case 'moduleUnfoldOpen':
moduleUnfoldOpen.value = !moduleUnfoldOpen.value
break
}
}
const setFormStyle = (key) => {
formStyle.value = key
}
const setUserInfo = (key) => {
userInfo.value = key
}
const setSysBaseConfig = (key) => {
sysBaseConfig.value = key
}
const setModule = (key) => {
module.value = key
}
return {
isMobile,
layout,
menuIsCollapse,
sideUniqueOpen,
layoutTagsOpen,
breadcrumbOpen,
fixedWidth,
topHeaderThemeColorOpen,
topHeaderThemeColorSpread,
loginUserWatermarkOpen,
footerCopyrightOpen,
moduleUnfoldOpen,
theme,
themeColor,
roundedCornerStyleOpen,
formStyle,
userInfo,
sysBaseConfig,
module,
setIsMobile,
setLayout,
setTheme,
setThemeColor,
initTheme,
toggleConfig,
setFormStyle,
setUserInfo,
setSysBaseConfig,
setModule
}
})
export const useGlobalStore = globalStore

View File

@@ -0,0 +1,58 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { defineStore } from 'pinia'
export const iframeStore = defineStore('iframe', () => {
// 定义state
const iframeList = ref([])
const setIframeList = (route) => {
iframeList.value = []
iframeList.value.push(route)
}
// 定义action
const pushIframeList = (route) => {
const target = iframeList.value.find((item) => item.path === route.path)
if (!target) {
iframeList.value.push(route)
}
}
const removeIframeList = (route) => {
iframeList.value.forEach((item, index) => {
if (item.path === route.path) {
iframeList.value.splice(index, 1)
}
})
}
const refreshIframe = (route) => {
iframeList.value.forEach((item) => {
if (item.path === route.path) {
const url = route.meta.url
item.meta.url = ''
setTimeout(() => {
item.meta.url = url
}, 200)
}
})
}
const clearIframeList = () => {
iframeList.value = []
}
return {
iframeList,
setIframeList,
pushIframeList,
removeIframeList,
refreshIframe,
clearIframeList
}
})

View File

@@ -0,0 +1,5 @@
export * from './global'
export * from './search'
export * from './iframe'
export * from './keepAlive'
export * from './viewTags'

View File

@@ -0,0 +1,55 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { defineStore } from 'pinia'
export const keepAliveStore = defineStore('keepAlive', () => {
// 定义state
const keepLiveRoute = ref([])
const routeKey = ref(null)
const routeShow = ref(true)
// 定义action
const pushKeepLive = (component) => {
if (!keepLiveRoute.value.includes(component)) {
keepLiveRoute.value.push(component)
}
}
const removeKeepLive = (component) => {
const index = keepLiveRoute.value.indexOf(component)
if (index !== -1) {
keepLiveRoute.value.splice(index, 1)
}
}
const clearKeepLive = () => {
keepLiveRoute.value = []
}
const setRouteKey = (key) => {
routeKey.value = key
}
const setRouteShow = (key) => {
routeShow.value = key
}
const setRouteKeyAction = (key) => {
setRouteKey(key)
}
return {
keepLiveRoute,
routeKey,
routeShow,
pushKeepLive,
removeKeepLive,
clearKeepLive,
setRouteKey,
setRouteShow,
setRouteKeyAction
}
})

156
nl-vue/src/store/menu.js Normal file
View File

@@ -0,0 +1,156 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { defineStore } from 'pinia'
import tool from '@/utils/tool'
import { cloneDeep } from 'lodash-es'
import userRoutes from '@/config/route'
import { searchStore } from '@/store/search'
import router from '@/router'
import userCenterApi from '@/api/sys/userCenterApi'
import whiteList from '@/router/whiteList'
import routesData from '@/router/systemRouter'
// findPwd和login路由组件已静态加载此处不在进行异步加载
const modules = import.meta.glob([
'/src/views/**/**.vue',
'!/src/views/auth/findPwd/**.vue',
'!/src/views/auth/login/**.vue'
])
export const useMenuStore = defineStore('menuStore', () => {
const menuData = ref([])
const refreshFlag = ref(false)
// 改变刷新标志
const changeRefreshFlag = (flag) => {
refreshFlag.value = flag
}
// 加载菜单
const loadMenu = () => {
// 获取用户菜单
const apiMenu = tool.data.get('MENU') || []
if (apiMenu.length === 0) {
// 创建默认模块,显示默认菜单
apiMenu[0] = cloneDeep(userRoutes.module[0])
}
const childrenApiMenu = apiMenu[0].children
apiMenu[0].children = [...(childrenApiMenu ? childrenApiMenu : []), ...userRoutes.menu]
let menuRouter = filterAsyncRouter(apiMenu)
menuRouter = flatAsyncRoutes(menuRouter)
menuData.value = menuRouter
// 初始化搜索
const search_store = searchStore()
search_store.init(menuRouter)
}
// 过滤异步路由
const filterAsyncRouter = (routerMap) => {
const accessedRouters = []
routerMap.forEach((item) => {
item.meta = item.meta ? item.meta : {}
// 处理外部链接特殊路由
if (item.meta.type === 'iframe') {
item.meta.url = item.path
item.path = `/${item.name}`
}
// MAP转路由对象
const route = {
path: item.path,
name: item.name,
meta: item.meta,
redirect: item.redirect,
children: item.children ? filterAsyncRouter(item.children) : null,
component: loadComponent(item.component)
}
accessedRouters.push(route)
})
return accessedRouters
}
// 将异步路由扁平化
const flatAsyncRoutes = (routes, breadcrumb = []) => {
const res = []
routes.forEach((route) => {
const tmp = { ...route }
if (tmp.children) {
const childrenBreadcrumb = [...breadcrumb]
childrenBreadcrumb.push(route)
const tmpRoute = { ...route }
tmpRoute.meta.breadcrumb = childrenBreadcrumb
delete tmpRoute.children
res.push(tmpRoute)
const childrenRoutes = flatAsyncRoutes(tmp.children, childrenBreadcrumb)
childrenRoutes.map((item) => {
res.push(item)
})
} else {
const tmpBreadcrumb = [...breadcrumb]
tmpBreadcrumb.push(tmp)
tmp.meta.breadcrumb = tmpBreadcrumb
res.push(tmp)
}
})
return res
}
// 动态加载组件
const loadComponent = (component) => {
if (component) {
if (component.includes('/')) {
return modules[`/src/views/${component}.vue`]
}
return modules[`/src/views/${component}/index.vue`]
} else {
return () => import(/* @vite-ignore */ `/src/layout/other/empty.vue`)
}
}
// 从路由中移除菜单
const removeFromRouter = () => {
const routes = router.getRoutes()
// 遍历所有路由
routes.forEach((route) => {
// 过滤白名单
if (
whiteList.filter((e) => e.path === route.path).length > 0 ||
routesData.filter((e) => e.path === route.path).length > 0
) {
return
}
if (route.name && route.name !== 'layout') {
router.removeRoute(route.name)
}
})
}
// 获取用户菜单通过API重新初始化菜单用于界面实时响应
const fetchMenu = async () => {
const menu = await userCenterApi.userLoginMenu()
tool.data.set('MENU', menu)
refreshMenu()
}
// 刷新菜单非API刷新用于路由守卫内使用
const refreshMenu = () => {
loadMenu()
removeFromRouter()
addToRouter()
changeRefreshFlag(true)
}
// 通过API刷新菜单仅在layout的onMounted内使用浏览器刷新只刷新一次
const refreshApiMenu = () => {
userCenterApi.userLoginMenu().then((data) => {
tool.data.set('MENU', data)
nextTick(() => {
refreshMenu()
})
})
}
// 将菜单添加到路由
const addToRouter = () => {
menuData.value.forEach((item) => {
router.addRoute('layout', item)
})
}
return { menuData, loadMenu, addToRouter, refreshMenu, changeRefreshFlag, refreshFlag, fetchMenu, refreshApiMenu }
})

View File

@@ -0,0 +1,63 @@
import '@/utils/objects'
import { defineStore } from 'pinia'
export const searchStore = defineStore('search', () => {
// 定义state
const pool = ref([])
const hotkey = ref({
open: 's',
close: 'esc'
})
const active = ref(false)
// 定义action
const toggleActive = () => {
active.value = !active.value
}
const setActive = (val) => {
active.value = val
}
const init = (menu) => {
const poolList = []
const getFullName = function (meta) {
if (meta.breadcrumb) {
let list = []
meta.breadcrumb.forEach((item) => {
list.push(item.meta.title)
})
return list.join(' / ')
}
return meta.title
}
const push = function (menu) {
menu.forEach((m) => {
if ('menu' === m.meta.type) {
if (m.children) {
push(m.children)
} else if (m.children === null) {
poolList.push({
icon: m.meta.icon,
path: m.path,
fullPath: m.path,
name: m.meta.title,
fullName: getFullName(m.meta),
namePinyin: m.meta.title.toPinyin(),
namePinyinFirst: m.meta.title.toPinyin(true)
})
}
}
})
}
push(menu)
pool.value = poolList
}
return {
pool,
hotkey,
active,
toggleActive,
setActive,
init
}
})

View File

@@ -0,0 +1,40 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import configApi from '@/api/dev/configApi'
import { message } from 'ant-design-vue'
const formData = ref({
SNOWY_SYS_LOGO: '',
SNOWY_SYS_BACK_IMAGE: '',
SNOWY_SYS_NAME: '',
SNOWY_SYS_VERSION: '',
SNOWY_SYS_COPYRIGHT: '',
SNOWY_SYS_COPYRIGHT_URL: '',
SNOWY_SYS_DEFAULT_FILE_ENGINE: 'LOCAL',
SNOWY_SYS_DEFAULT_CAPTCHA_OPEN: false,
SNOWY_SYS_DEFAULT_PASSWORD: ''
})
const param = {
category: 'SYS_BASE'
}
const getSysBaseConfig = () => {
configApi.configList(param).then((data) => {
if (data) {
data.forEach((item) => {
formData.value[item.configKey] = item.configValue ? '' : item.configValue
})
} else {
message.warning('表单项不存在,请初始化数据库')
}
})
}

32
nl-vue/src/store/user.js Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { defineStore } from 'pinia'
import loginApi from '@/api/auth/loginApi'
import { useGlobalStore } from '@/store'
import tool from '@/utils/tool'
export const userStore = defineStore('userStore', () => {
// 初始化用户信息
const initUserInfo = async () => {
const data = await loginApi.getLoginUser()
const globalStore = useGlobalStore()
globalStore.setUserInfo(data)
tool.data.set('USER_INFO', data)
}
// 刷新登录用户信息
const refreshUserLoginUserInfo = () => {
loginApi.getLoginUser().then((data) => {
const globalStore = useGlobalStore()
globalStore.setUserInfo(data)
tool.data.set('USER_INFO', data)
})
}
return { initUserInfo, refreshUserLoginUserInfo }
})

View File

@@ -0,0 +1,90 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { defineStore } from 'pinia'
export const viewTagsStore = defineStore('viewTags', () => {
// 定义state
const viewTags = ref([])
// 定义action
const pushViewTags = (route) => {
const target = viewTags.value.find((item) => item.path === route.path)
const isName = route.name
if (!target && isName) {
viewTags.value.push(route)
}
if (target) {
viewTags.value.forEach((item, index) => {
if (item.path === route.path) {
viewTags.value[index] = { ...route, ...item }
// Object.assign(item, route)
}
})
}
}
const removeViewTags = (route) => {
viewTags.value.forEach((item, index) => {
if (item.fullPath === route.fullPath) {
viewTags.value.splice(index, 1)
}
})
}
const updateViewTags = (route) => {
viewTags.value.forEach((item, index) => {
if (item.fullPath === route.fullPath) {
viewTags.value[index] = { ...route, ...item }
// Object.assign(item, route)
}
})
}
// 更新或删除视图标签
const updateOrRemoveViewTags = (routes) => {
if (routes && routes.length > 0) {
viewTags.value.forEach((item, index) => {
const target = routes.find((route) => route.path === item.fullPath)
if (!target) {
// 路由不存在,删除
viewTags.value.splice(index, 1)
} else {
// 路由存在,更新
viewTags.value = viewTags.value.map((item) => {
if (item.fullPath === target.path) {
return { ...item, meta: target.meta }
}
return item
})
}
})
}
}
const updateViewTagsTitle = (title = '') => {
const nowFullPath = location.hash.substring(1)
viewTags.value.forEach((item) => {
if (item.fullPath === nowFullPath) {
item.meta.key = Date.now()
item.meta.title = title
}
})
}
const clearViewTags = () => {
viewTags.value = []
}
return {
viewTags,
pushViewTags,
removeViewTags,
updateViewTags,
updateViewTagsTitle,
clearViewTags,
updateOrRemoveViewTags
}
})