diff --git a/.env b/.env index b48df03..c2e5f60 100644 --- a/.env +++ b/.env @@ -9,3 +9,6 @@ VITE_AUTH_ROUTE_MODE=static # 设置登陆后跳转地址 VITE_HOME_PATH = /dashboard/workbench + +# 本地存储前缀 +VITE_STORAGE_PREFIX= diff --git a/.env.prod b/.env.prod index dcfba5c..29a3c4e 100644 --- a/.env.prod +++ b/.env.prod @@ -1,5 +1,5 @@ # 是否开启压缩资源 -VITE_COMPRESS_OPEN=N +VITE_BUILD_COMPRESS=N # 压缩算法 gzip | brotliCompress | deflate | deflateRaw VITE_COMPRESS_TYPE=gzip diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..29a3c4e --- /dev/null +++ b/.env.test @@ -0,0 +1,6 @@ +# 是否开启压缩资源 +VITE_BUILD_COMPRESS=N + +# 压缩算法 gzip | brotliCompress | deflate | deflateRaw +VITE_COMPRESS_TYPE=gzip + diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9fdd938 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://cdn.jsdelivr.net/gh/chansee97/static/sponsor-wechat.png', 'https://cdn.jsdelivr.net/gh/chansee97/static/sponsor-alipay.png'] diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..4e43c94 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,42 @@ +name: 🐞 Bug report +description: Create a report to help us improve +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: textarea + id: bug-description + attributes: + label: Description + description: Please explain clearly how the bug reappears. If possible, it is best to add the cause of the problem. + placeholder: bug description + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected + validations: + required: true + + - type: textarea + id: additional-comments + attributes: + label: Additional comments + description: e.g. some background/context of how you ran into this bug. + + - type: checkboxes + id: checkboxes + attributes: + label: Validations + description: Before submitting the issue, please make sure you do the following + options: + - label: Ensure this issue not a bug proposal. + required: true + - label: Read the [docs](https://nova-admin-docs.netlify.app/). + required: true + - label: Check that there isn't [already an issue](https://github.com/chansee97/nova-admin/issues) that descript the same thing to avoid creating a duplicate. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..fe5870d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,45 @@ +name: ✨ New feature + +description: Propose a new feature to be added to Nova-admin + +body: + - type: markdown + attributes: + value: | + Thanks for your interest in the project and taking the time to fill out this feature report! + + - type: textarea + id: feature-description + attributes: + label: Description + description: Clear and concise description of the problem. Please make the reason and usecases as detailed as possible. If you intend to submit a PR for this issue, tell us in the description. Thanks! + placeholder: As a developer using Nova-admin I want [goal / wish] so that [benefit]... + validations: + required: true + + - type: textarea + id: suggested-solution + attributes: + label: Suggestion + description: In module [xy] we could provide following implementation... + validations: + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Any other context or screenshots about the feature request here. + + - type: checkboxes + id: checkboxes + attributes: + label: Validations + description: Before submitting the issue, please make sure you do the following + options: + - label: Ensure this issue not a feature proposal. + required: true + - label: Read the [docs](https://nova-admin-docs.netlify.app/). + required: true + - label: Check that there isn't [already an issue](https://github.com/chansee97/nova-admin/issues) that descript the same thing to avoid creating a duplicate. + required: true diff --git a/.github/ISSUE_TEMPLATE/others.yml b/.github/ISSUE_TEMPLATE/others.yml new file mode 100644 index 0000000..4da50fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/others.yml @@ -0,0 +1,31 @@ +name: 👓 Others + +description: Create an issue for Nova-admin + +body: + - type: markdown + attributes: + value: | + Thanks for your interest in the project and taking the time to create this issue! + + - type: textarea + id: description + attributes: + label: Description + description: Clear and concise description of the issue. Thanks! + placeholder: There are some thing I want to ... + validations: + required: true + + - type: checkboxes + id: checkboxes + attributes: + label: Validations + description: Before submitting the issue, please make sure you do the following + options: + - label: Ensure this issue neither a bug report nor a feature proposal. + required: true + - label: Read the [docs](https://nova-admin-docs.netlify.app/). + required: true + - label: Check that there isn't [already an issue](https://github.com/chansee97/nova-admin/issues) that descript the same thing to avoid creating a duplicate. + required: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..546964b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Release + +permissions: + contents: write + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v3 + with: + node-version: 16.x + + - run: npx changelogithub # or changelogithub@0.12 if ensure the stable result + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.vscode/settings.json b/.vscode/settings.json index abd048b..d65a14b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -65,5 +65,14 @@ "jsonc", "yaml", "toml" - ] + ], + "i18n-ally.sourceLanguage": "zh_CN", + "i18n-ally.displayLanguage": "zh_CN", + "i18n-ally.enabledFrameworks": ["vue"], + "i18n-ally.editor.preferEditor": true, + "i18n-ally.keystyle": "nested", + "i18n-ally.localesPaths": [ + "locales" + ], + "commentTranslate.source": "Google" } diff --git a/README.md b/README.md index 35663ae..16f2f78 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - Dark theme adaptation, maintaining the Naive style for interface aesthetics. - Only performs eslint validation during submission without excessive restrictions for simpler development. - Flexible and configurable interface style layout. +- Multilanguage (i18n) support. ## Project preview @@ -46,13 +47,16 @@ - [Gitee](https://gitee.com/chansee97/nova-admin) - [Github](https://github.com/chansee97/nova-admin) -## Related projects +## Interface document -- [Nova-admin-nest](https://github.com/chansee97/nove-admin-nest) (under development) Nova-Admin supporting background project based on TS, NestJs, typeorm +This project uses ApiFox for interface mock, check the online documentation for more interface details +[online aipfox docs](https://apifox.com/apidoc/shared-2b1abeb5-6e78-425e-a4ff-d7277ae83bf0) ## Install and use -The local development environment is recommended to use pnpm 8.x, Node.js 18.x +The local development environment is recommended to use pnpm 8.x, Node.js must be version 20.x. + +It is recommended to directly download the compressed package from [Releases](https://github.com/chansee97/nova-admin/releases) ```bash # install dependencies @@ -66,10 +70,9 @@ pnpm build ``` -## Interface document +## Related projects -This project uses ApiFox for interface mock, check the online documentation for more interface details -[online aipfox docs](https://apifox.com/apidoc/shared-2b1abeb5-6e78-425e-a4ff-d7277ae83bf0) +- [Nova-admin-nest](https://github.com/chansee97/nove-admin-nest) (under development) Nova-Admin supporting background project based on TS, NestJs, typeorm ## Learn to communicate @@ -93,9 +96,13 @@ If you feel that this project is helpful for your work or study, please help me | :--: |:--: | | | | -## [Contributors](https://github.com/chansee97/nova-admin/graphs/contributors) +## Contributors -![Contributors](https://contrib.rocks/image?repo=chansee97/nova-admin) +Thanks for all their contributions! + + + contributors + ## Star History diff --git a/README.zh-CN.md b/README.zh-CN.md index a230474..e7c1cdb 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -31,6 +31,7 @@ - 黑暗主题适配, 界面样式保持Naive风格 - 仅在提交时进行eslint校验,没有过多限制,开发更简便 - 界面样式布局灵活可配置 +- 多语言(i18n)支持 ## 项目预览 @@ -46,13 +47,16 @@ - [Gitee](https://gitee.com/chansee97/nova-admin) - [Github](https://github.com/chansee97/nova-admin) -## 相关项目 +## 接口文档 -- [Nova-admin-nest](https://github.com/chansee97/nove-admin-nest) (开发中)基于TS, NestJs, typeorm的Nova-Admin配套后台项目 +本项目使用ApiFox进行接口mock,查看在线文档以了解更多接口详情 +[在线apifox文档](https://apifox.com/apidoc/shared-2b1abeb5-6e78-425e-a4ff-d7277ae83bf0) ## 安装使用 -本地开发环境建议使用 pnpm 8.x 、Node.js 18.x +本地开发环境建议使用 pnpm 8.x 、Node.js 必须 20.x + +推荐直接下载[Releases](https://github.com/chansee97/nova-admin/releases)压缩包 ```bash # install dependencies @@ -66,10 +70,9 @@ pnpm build ``` -## 接口文档 +## 相关项目 -本项目使用ApiFox进行接口mock,查看在线文档以了解更多接口详情 -[在线apifox文档](https://apifox.com/apidoc/shared-2b1abeb5-6e78-425e-a4ff-d7277ae83bf0) +- [Nova-admin-nest](https://github.com/chansee97/nove-admin-nest) (开发中)基于TS, NestJs, typeorm的Nova-Admin配套后台项目 ## 学习交流 @@ -93,9 +96,13 @@ Nova-Admin 是完全开源免费的项目,目前仍然在优化迭代中,旨 | :--: |:--: | | | | -## [贡献者](https://github.com/chansee97/nova-admin/graphs/contributors) +## 贡献者 -![Contributors](https://contrib.rocks/image?repo=chansee97/nova-admin) +感谢他们的所做的一切贡献! + + + contributors + ## Star 历史 diff --git a/build/plugins.ts b/build/plugins.ts index 5b6eae5..3ef7498 100644 --- a/build/plugins.ts +++ b/build/plugins.ts @@ -1,4 +1,3 @@ -import path from 'node:path' import UnoCSS from '@unocss/vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' @@ -7,13 +6,11 @@ import AutoImport from 'unplugin-auto-import/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import viteCompression from 'vite-plugin-compression' import Icons from 'unplugin-icons/vite' +import { FileSystemIconLoader } from 'unplugin-icons/loaders' // https://github.com/antfu/unplugin-icons import IconsResolver from 'unplugin-icons/resolver' -// https://github.com/vbenjs/vite-plugin-svg-icons/blob/main/README.zh_CN.md -import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' - /** * @description: 设置vite插件配置 * @param {*} env - 环境变量配置 @@ -30,7 +27,7 @@ export function createVitePlugins(env: ImportMetaEnv) { // auto import api of lib AutoImport({ - imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'], + imports: ['vue', 'vue-router', 'pinia', '@vueuse/core', 'vue-i18n'], include: [ /\.[tj]sx?$/, /\.vue$/, @@ -43,28 +40,31 @@ export function createVitePlugins(env: ImportMetaEnv) { // auto import components lib Components({ dts: 'src/typings/components.d.ts', - resolvers: [IconsResolver(), NaiveUiResolver()], + resolvers: [ + IconsResolver({ + prefix: false, + customCollections: [ + 'svg-icons', + ], + }), + NaiveUiResolver(), + ], }), // auto import iconify's icons Icons({ defaultStyle: 'display:inline-block', compiler: 'vue3', + customCollections: { + 'svg-icons': FileSystemIconLoader( + 'src/assets/svg-icons', + svg => svg.replace(/^ -import { darkTheme, dateZhCN, zhCN } from 'naive-ui' +import { darkTheme } from 'naive-ui' import { useAppStore } from './store' +import { naiveI18nOptions } from '@/utils' -const locale = zhCN -const dateLocale = dateZhCN const appStore = useAppStore() + +const naiveLocale = computed(() => { + return naiveI18nOptions[appStore.lang] ? naiveI18nOptions[appStore.lang] : naiveI18nOptions.enUS +}, +) diff --git a/src/components/common/HelpInfo.vue b/src/components/common/HelpInfo.vue new file mode 100644 index 0000000..33e72fd --- /dev/null +++ b/src/components/common/HelpInfo.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/custom/IconSelect/icons.ts b/src/components/common/IconSelect/icons.ts similarity index 100% rename from src/components/custom/IconSelect/icons.ts rename to src/components/common/IconSelect/icons.ts diff --git a/src/components/custom/IconSelect/index.vue b/src/components/common/IconSelect/index.vue similarity index 52% rename from src/components/custom/IconSelect/index.vue rename to src/components/common/IconSelect/index.vue index 3750a40..7ba2c2b 100644 --- a/src/components/custom/IconSelect/index.vue +++ b/src/components/common/IconSelect/index.vue @@ -1,42 +1,49 @@
- +
- +
diff --git a/src/components/common/LangsSwitch.vue b/src/components/common/LangsSwitch.vue new file mode 100644 index 0000000..5f8f115 --- /dev/null +++ b/src/components/common/LangsSwitch.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/components/custom/EIcon.vue b/src/components/common/NovaIcon.vue similarity index 79% rename from src/components/custom/EIcon.vue rename to src/components/common/NovaIcon.vue index f3aee98..8fc1283 100644 --- a/src/components/custom/EIcon.vue +++ b/src/components/common/NovaIcon.vue @@ -2,19 +2,23 @@ import { Icon } from '@iconify/vue' interface iconPorps { + /* 图标名称 */ icon?: string + /* 图标颜色 */ color?: string + /* 图标大小 */ size?: number + /* 图标深度 */ depth?: 1 | 2 | 3 | 4 | 5 } const props = withDefaults(defineProps(), { size: 18, - icon: 'icon-park-outline:baby-feet', }) - - diff --git a/src/layouts/components/common/LayoutSelector.vue b/src/layouts/components/common/LayoutSelector.vue new file mode 100644 index 0000000..1520594 --- /dev/null +++ b/src/layouts/components/common/LayoutSelector.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/layouts/components/common/NoticeList.vue b/src/layouts/components/common/NoticeList.vue index 7da96a6..9f8d1ec 100644 --- a/src/layouts/components/common/NoticeList.vue +++ b/src/layouts/components/common/NoticeList.vue @@ -21,7 +21,7 @@ interface Emits { @@ -34,7 +34,6 @@ const appStore = useAppStore() } .list-move, -/* 对移动中的元素应用的过渡 */ .list-enter-active, .list-leave-active { transition: all 0.3s ease; @@ -42,7 +41,7 @@ const appStore = useAppStore() .list-enter-from,.list-leave-to { opacity: 0; - transform: translateY(-30px); + transform: translateX(-30px); } .list-leave-active { diff --git a/src/layouts/components/header/CollapaseButton.vue b/src/layouts/components/header/CollapaseButton.vue index febfb0d..f39ef0b 100644 --- a/src/layouts/components/header/CollapaseButton.vue +++ b/src/layouts/components/header/CollapaseButton.vue @@ -8,11 +8,11 @@ const appStore = useAppStore() - 切换侧边栏 + {{ $t('app.toggleSider') }} diff --git a/src/layouts/components/header/FullScreen.vue b/src/layouts/components/header/FullScreen.vue index 22ffa56..9409809 100644 --- a/src/layouts/components/header/FullScreen.vue +++ b/src/layouts/components/header/FullScreen.vue @@ -8,11 +8,11 @@ const appStore = useAppStore() - 全屏 + {{ $t('app.toggleFullScreen') }} diff --git a/src/layouts/components/header/Notices.vue b/src/layouts/components/header/Notices.vue index 7e1dce1..eacfab4 100644 --- a/src/layouts/components/header/Notices.vue +++ b/src/layouts/components/header/Notices.vue @@ -81,11 +81,10 @@ const MassageData = ref([ ]) const currentTab = ref(0) function handleRead(id: number) { - // MassageData.value[currentTab.value].list[index].isRead = true const data = MassageData.value.find(i => i.id === id) if (data) data.isRead = true - window.$message.success(`已读id: ${id}`) + window.$message.success(`id: ${id}`) } const massageCount = computed(() => { return MassageData.value.filter(i => !i.isRead).length @@ -102,18 +101,18 @@ const groupMessage = computed(() => { - 消息通知 + {{ $t('app.notificationsTips') }} @@ -122,7 +121,7 @@ const groupMessage = computed(() => { @@ -131,7 +130,7 @@ const groupMessage = computed(() => { diff --git a/src/layouts/components/header/Search.vue b/src/layouts/components/header/Search.vue index d0a2e34..e840ec2 100644 --- a/src/layouts/components/header/Search.vue +++ b/src/layouts/components/header/Search.vue @@ -6,16 +6,18 @@ import { renderIcon } from '@/utils' const routeStore = useRouteStore() const searchValue = ref('') +const { t } = useI18n() + const options = computed(() => { return routeStore.rowRoutes.filter((item) => { const conditions = [ - item['meta.title']?.includes(searchValue.value), + t(`route.${String(item.name)}`, item['meta.title'] || item.name)?.includes(searchValue.value), item.path?.includes(searchValue.value), ] return conditions.some(condition => condition) }).map((item) => { return { - label: item['meta.title'], + label: t(`route.${String(item.name)}`, item['meta.title'] || item.name), value: item.path, icon: item['meta.icon'], } @@ -44,11 +46,11 @@ function handleSelect(value: string) { diff --git a/src/layouts/components/header/Setting.vue b/src/layouts/components/header/Setting.vue index c4b6aca..672a374 100644 --- a/src/layouts/components/header/Setting.vue +++ b/src/layouts/components/header/Setting.vue @@ -1,42 +1,48 @@ @@ -64,14 +80,17 @@ function handleSelect(key: string | number) { :options="options" @select="handleSelect" > - - - {{ userInfo?.nickname }} - + + + diff --git a/src/layouts/components/sider/Logo.vue b/src/layouts/components/sider/Logo.vue index 342de95..ed65b26 100644 --- a/src/layouts/components/sider/Logo.vue +++ b/src/layouts/components/sider/Logo.vue @@ -9,16 +9,13 @@ const name = import.meta.env.VITE_APP_NAME diff --git a/src/layouts/components/tab/DropTabs.vue b/src/layouts/components/tab/DropTabs.vue new file mode 100644 index 0000000..1335f02 --- /dev/null +++ b/src/layouts/components/tab/DropTabs.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/layouts/components/tab/Reload.vue b/src/layouts/components/tab/Reload.vue index 9c49b6c..aae2f83 100644 --- a/src/layouts/components/tab/Reload.vue +++ b/src/layouts/components/tab/Reload.vue @@ -18,10 +18,10 @@ function handleReload() { - 刷新页面 + {{ $t('common.reload') }} diff --git a/src/layouts/components/tab/TabBar.vue b/src/layouts/components/tab/TabBar.vue index b5f96de..4753fe7 100644 --- a/src/layouts/components/tab/TabBar.vue +++ b/src/layouts/components/tab/TabBar.vue @@ -1,8 +1,14 @@ - +./DropTabs.vue diff --git a/src/layouts/index.ts b/src/layouts/index.ts deleted file mode 100644 index 20b9c8b..0000000 --- a/src/layouts/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -const BasicLayout = () => import('./BasicLayout/index.vue') - -export { BasicLayout } diff --git a/src/layouts/index.vue b/src/layouts/index.vue new file mode 100644 index 0000000..9280390 --- /dev/null +++ b/src/layouts/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/layouts/BasicLayout/index.vue b/src/layouts/leftMenu.layout.vue similarity index 95% rename from src/layouts/BasicLayout/index.vue rename to src/layouts/leftMenu.layout.vue index 45dc613..36ab5c8 100644 --- a/src/layouts/BasicLayout/index.vue +++ b/src/layouts/leftMenu.layout.vue @@ -11,7 +11,7 @@ import { Setting, TabBar, UserCenter, -} from '../components' +} from './components' import { useAppStore, useRouteStore } from '@/store' const routeStore = useRouteStore() @@ -49,11 +49,12 @@ const appStore = useAppStore() -
+
+
diff --git a/src/layouts/topMenu.layout.vue b/src/layouts/topMenu.layout.vue new file mode 100644 index 0000000..db1fa8c --- /dev/null +++ b/src/layouts/topMenu.layout.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/main.ts b/src/main.ts index daa2839..aec780f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import type { App } from 'vue' import AppVue from './App.vue' -import AppLoading from './components/common/appLoading.vue' +import AppLoading from './components/common/AppLoading.vue' import { installRouter } from '@/router' import { installPinia } from '@/store' @@ -18,7 +18,7 @@ async function setupApp() { // 注册模块 Vue-router await installRouter(app) - /* 注册模块 Vue-router/Pinia */ + /* 注册模块 指令/静态资源 */ Object.values( import.meta.glob<{ install: (app: App) => void }>('./modules/*.ts', { eager: true, diff --git a/src/modules/assets.ts b/src/modules/assets.ts index 6817e28..3c4eb31 100644 --- a/src/modules/assets.ts +++ b/src/modules/assets.ts @@ -1,6 +1,5 @@ import 'uno.css' import '@/styles/index.css' -import 'virtual:svg-icons-register' // 全局引入的静态资源 export function install() { diff --git a/src/modules/i18n.ts b/src/modules/i18n.ts new file mode 100644 index 0000000..eeed532 --- /dev/null +++ b/src/modules/i18n.ts @@ -0,0 +1,19 @@ +import { createI18n } from 'vue-i18n' +import type { App } from 'vue' +import enUS from '../../locales/en_US.json' +import zhCN from '../../locales/zh_CN.json' +import { local } from '@/utils' + +export const i18n = createI18n({ + legacy: false, + locale: local.get('lang') || 'enUS', // 默认显示语言 + fallbackLocale: 'enUS', + messages: { + zhCN, + enUS, + }, +}) + +export function install(app: App) { + app.use(i18n) +} diff --git a/src/modules/nprogress.ts b/src/modules/nprogress.ts deleted file mode 100644 index 0cf5a1d..0000000 --- a/src/modules/nprogress.ts +++ /dev/null @@ -1,11 +0,0 @@ -import NProgress from 'nprogress' -import { useAppStore } from '@/store' - -export function install() { - // 初始载入,初始化body的css变量 - const appStore = useAppStore() - appStore.setPrimaryColor(appStore.primaryColor) - - NProgress.configure({ easing: 'ease', speed: 500 }) - window.$NProgress = NProgress -} diff --git a/src/router/guard.ts b/src/router/guard.ts index 3023755..9314fca 100644 --- a/src/router/guard.ts +++ b/src/router/guard.ts @@ -15,11 +15,11 @@ export function setupRouterGuard(router: Router) { window.open(to.meta.herf) return false } - // 开始 NProgress - appStore.showProgress && window.$NProgress?.start() + // 开始 loadingBar + appStore.showProgress && window.$loadingBar?.start() // 判断有无TOKEN,登录鉴权 - const isLogin = Boolean(local.get('token')) + const isLogin = Boolean(local.get('accessToken')) if (!isLogin) { if (to.name === 'login') next() @@ -67,7 +67,7 @@ export function setupRouterGuard(router: Router) { router.afterEach((to) => { // 修改网页标题 document.title = `${to.meta.title} - ${title}` - // 结束 NProgress - appStore.showProgress && window.$NProgress?.done() + // 结束 loadingBar + appStore.showProgress && window.$loadingBar?.finish() }) } diff --git a/src/router/routes.inner.ts b/src/router/routes.inner.ts index 162f5b2..868ac9d 100644 --- a/src/router/routes.inner.ts +++ b/src/router/routes.inner.ts @@ -6,7 +6,7 @@ export const routes: RouteRecordRaw[] = [ path: '/', name: 'root', redirect: '/appRoot', - component: () => import('@/layouts/index'), + // component: () => import('@/layouts/index'), children: [ ], }, diff --git a/src/router/routes.static.ts b/src/router/routes.static.ts index 3854085..b406b5f 100644 --- a/src/router/routes.static.ts +++ b/src/router/routes.static.ts @@ -5,27 +5,30 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '仪表盘', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:analysis', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 1, 'pid': null, }, { - 'name': 'dashboard_workbench', + 'name': 'workbench', 'path': '/dashboard/workbench', 'meta.title': '工作台', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:alarm', 'meta.pinTab': true, + 'meta.menuType': 'page', 'componentPath': '/dashboard/workbench/index.vue', 'id': 2, 'pid': 1, }, { - 'name': 'dashboard_monitor', + 'name': 'monitor', 'path': '/dashboard/monitor', 'meta.title': '监控页', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:anchor', + 'meta.menuType': 'page', 'componentPath': '/dashboard/monitor/index.vue', 'id': 3, 'pid': 1, @@ -36,6 +39,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '多级菜单演示', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:list', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 4, 'pid': null, @@ -46,18 +50,20 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '多级菜单子页', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:list', + 'meta.menuType': 'page', 'componentPath': '/test/test2/index.vue', 'id': 6, 'pid': 4, }, { - 'name': 'test2_detail', + 'name': 'test2Detail', 'path': '/test/test2/detail', 'meta.title': '多级菜单的详情页', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:list', 'meta.hide': true, 'meta.activeMenu': '/test/test2', + 'meta.menuType': 'page', 'componentPath': '/test/test2/detail/index.vue', 'id': 7, 'pid': 4, @@ -68,6 +74,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '多级菜单', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:list', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 8, 'pid': 4, @@ -88,12 +95,13 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '列表页', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:list-two', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 10, 'pid': null, }, { - 'name': 'list_commonList', + 'name': 'commonList', 'path': '/list/commonList', 'meta.title': '常用列表', 'meta.requiresAuth': true, @@ -103,7 +111,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'pid': 10, }, { - 'name': 'list_cardList', + 'name': 'cardList', 'path': '/list/cardList', 'meta.title': '卡片列表', 'meta.requiresAuth': true, @@ -113,103 +121,105 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'pid': 10, }, { - 'name': 'plugin', - 'path': '/plugin', + 'name': 'demo', + 'path': '/demo', 'meta.title': '功能示例', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:application-one', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 13, 'pid': null, }, { 'name': 'fetch', - 'path': '/plugin/fetch', - 'meta.title': '接口功能测试', + 'path': '/demo/fetch', + 'meta.title': '请求示例', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:international', - 'componentPath': '/plugin/fetch/index.vue', + 'componentPath': '/demo/fetch/index.vue', 'id': 5, 'pid': 13, }, { - 'name': 'plugin_echarts', - 'path': '/plugin/echarts', + 'name': 'echarts', + 'path': '/demo/echarts', 'meta.title': 'ECharts', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:chart-proportion', - 'componentPath': '/plugin/echarts/index.vue', + 'componentPath': '/demo/echarts/index.vue', 'id': 15, 'pid': 13, }, { - 'name': 'PluginMap', - 'path': '/plugin/map', + 'name': 'map', + 'path': '/demo/map', 'meta.title': '地图', 'meta.requiresAuth': true, 'meta.icon': 'carbon:map', 'meta.keepAlive': true, - 'componentPath': '/plugin/map/index.vue', + 'componentPath': '/demo/map/index.vue', 'id': 17, 'pid': 13, }, { - 'name': 'plugin_editor', - 'path': '/plugin/editor', + 'name': 'editor', + 'path': '/demo/editor', 'meta.title': '编辑器', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:editor', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 18, 'pid': 13, }, { - 'name': 'plugin_md', - 'path': '/plugin/editor/md', + 'name': 'editorMd', + 'path': '/demo/editor/md', 'meta.title': 'MarkDown', 'meta.requiresAuth': true, 'meta.icon': 'ri:markdown-line', - 'componentPath': '/plugin/editor/md/index.vue', + 'componentPath': '/demo/editor/md/index.vue', 'id': 19, 'pid': 18, }, { - 'name': 'plugin_rich', - 'path': '/plugin/editor/rich', + 'name': 'editorRich', + 'path': '/demo/editor/rich', 'meta.title': '富文本', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:edit-one', - 'componentPath': '/plugin/editor/rich/index.vue', + 'componentPath': '/demo/editor/rich/index.vue', 'id': 20, 'pid': 18, }, { - 'name': 'plugin_clipboard', - 'path': '/plugin/clipboard', + 'name': 'clipboard', + 'path': '/demo/clipboard', 'meta.title': '剪贴板', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:clipboard', - 'componentPath': '/plugin/clipboard/index.vue', + 'componentPath': '/demo/clipboard/index.vue', 'id': 21, 'pid': 13, }, { - 'name': 'plugin_icons', - 'path': '/plugin/icons', + 'name': 'icons', + 'path': '/demo/icons', 'meta.title': '图标', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:winking-face-with-open-eyes', - 'componentPath': '/plugin/icons/index.vue', + 'componentPath': '/demo/icons/index.vue', 'id': 22, 'pid': 13, }, { - 'name': 'plugin_QRCode', - 'path': '/plugin/QRCode', + 'name': 'QRCode', + 'path': '/demo/QRCode', 'meta.title': '二维码', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:two-dimensional-code', - 'componentPath': '/plugin/QRCode/index.vue', + 'componentPath': '/demo/QRCode/index.vue', 'id': 23, 'pid': 13, }, @@ -219,14 +229,15 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '外链文档', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:file-doc', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 24, 'pid': null, }, { - 'name': 'docments_vue', + 'name': 'docmentsVue', 'path': '/docments/vue', - 'meta.title': 'vue', + 'meta.title': 'Vue', 'meta.requiresAuth': true, 'meta.icon': 'logos:vue', 'componentPath': '/docments/vue/index.vue', @@ -234,9 +245,9 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'pid': 24, }, { - 'name': 'docments_vite', + 'name': 'docmentsVite', 'path': '/docments/vite', - 'meta.title': 'vite', + 'meta.title': 'Vite', 'meta.requiresAuth': true, 'meta.icon': 'logos:vitejs', 'componentPath': '/docments/vite/index.vue', @@ -244,28 +255,29 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'pid': 24, }, { - 'name': 'docments_vueuse', + 'name': 'docmentsVueuse', 'path': '/docments/vueuse', 'meta.title': 'VueUse(外链)', 'meta.requiresAuth': true, 'meta.icon': 'logos:vueuse', 'meta.herf': 'https://vueuse.org/guide/', - 'componentPath': '/docments/vueuse/index.vue', + 'componentPath': 'null', 'id': 27, 'pid': 24, }, { 'name': 'permission', 'path': '/permission', - 'meta.title': '权限示例', + 'meta.title': '权限', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:people-safe', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 28, 'pid': null, }, { - 'name': 'permission_permission', + 'name': 'permissionDemo', 'path': '/permission/permission', 'meta.title': '权限示例', 'meta.requiresAuth': true, @@ -275,9 +287,9 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'pid': 28, }, { - 'name': 'permission_justSuper', + 'name': 'justSuper', 'path': '/permission/justSuper', - 'meta.title': '超管super可见', + 'meta.title': 'super可见', 'meta.requiresAuth': true, 'meta.roles': [ 'super', @@ -293,6 +305,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '异常页', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:error-computer', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 31, 'pid': null, @@ -300,7 +313,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ { 'name': 'demo403', 'path': '/error/403', - 'meta.title': '403页', + 'meta.title': '403', 'meta.requiresAuth': true, 'meta.icon': 'carbon:error', 'meta.order': 3, @@ -311,7 +324,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ { 'name': 'demo404', 'path': '/error/404', - 'meta.title': '404页', + 'meta.title': '404', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:error', 'meta.order': 2, @@ -322,7 +335,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ { 'name': 'demo500', 'path': '/error/500', - 'meta.title': '500页', + 'meta.title': '500', 'meta.requiresAuth': true, 'meta.icon': 'carbon:data-error', 'meta.order': 1, @@ -336,12 +349,13 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'meta.title': '系统设置', 'meta.requiresAuth': true, 'meta.icon': 'icon-park-outline:setting', + 'meta.menuType': 'dir', 'componentPath': null, 'id': 35, 'pid': null, }, { - 'name': 'setting_account', + 'name': 'accountSetting', 'path': '/setting/account', 'meta.title': '用户设置', 'meta.requiresAuth': true, @@ -351,7 +365,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'pid': 35, }, { - 'name': 'setting_dictionary', + 'name': 'dictionarySetting', 'path': '/setting/dictionary', 'meta.title': '字典设置', 'meta.requiresAuth': true, @@ -361,7 +375,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [ 'pid': 35, }, { - 'name': 'setting_menu', + 'name': 'menuSetting', 'path': '/setting/menu', 'meta.title': '菜单设置', 'meta.requiresAuth': true, diff --git a/src/service/api/list.ts b/src/service/api/list.ts index ce33ebc..dca2205 100644 --- a/src/service/api/list.ts +++ b/src/service/api/list.ts @@ -1,5 +1,5 @@ -import { alovaInstance } from '../http' +import { request } from '../http' export function fetchUserList() { - return alovaInstance.Get('/userList') + return request.Get('/userList') } diff --git a/src/service/api/login.ts b/src/service/api/login.ts index 04c42c3..09bd04b 100644 --- a/src/service/api/login.ts +++ b/src/service/api/login.ts @@ -1,19 +1,19 @@ -import { alovaInstance } from '../http' +import { request } from '../http' interface Ilogin { - username: string + userName: string password: string } export function fetchLogin(params: Ilogin) { - const methodInstance = alovaInstance.Post('/login', params) + const methodInstance = request.Post>('/login', params) methodInstance.meta = { authRole: null, } return methodInstance } -export function fetchUpdateToken(params: any) { - const method = alovaInstance.Post('/updateToken', params) +export function fetchUpdateToken(data: any) { + const method = request.Post>('/updateToken', data) method.meta = { authRole: 'refreshToken', } @@ -21,5 +21,5 @@ export function fetchUpdateToken(params: any) { } export function fetchUserRoutes(params: { id: number }) { - return alovaInstance.Get('/getUserRoutes', { params }) + return request.Get >('/getUserRoutes', { params }) } diff --git a/src/service/api/system.ts b/src/service/api/system.ts new file mode 100644 index 0000000..1fa9b9a --- /dev/null +++ b/src/service/api/system.ts @@ -0,0 +1,5 @@ +import { request } from '../http' + +export function fetchAllRoutes() { + return request.Get >('/getUserRoutes') +} diff --git a/src/service/api/test.ts b/src/service/api/test.ts index 9e9ab3c..c224885 100644 --- a/src/service/api/test.ts +++ b/src/service/api/test.ts @@ -1,17 +1,17 @@ -import { alovaInstance, blankInstance } from '../http' +import { blankInstance, request } from '../http' /* get方法测试 */ export function fetachGet(params?: any) { - return alovaInstance.Get('/getAPI', { params }) + return request.Get('/getAPI', { params }) } /* post方法测试 */ export function fetchPost(data: any) { - return alovaInstance.Post('/postAPI', data) + return request.Post('/postAPI', data) } /* formPost方法测试 */ export function fetchFormPost(data: any) { - const methodInstance = alovaInstance.Post('/postFormAPI', data) + const methodInstance = request.Post('/postFormAPI', data) methodInstance.meta = { isFormPost: true, } @@ -19,15 +19,15 @@ export function fetchFormPost(data: any) { } /* delete方法测试 */ export function fetchDelete() { - return alovaInstance.Delete('/deleteAPI') + return request.Delete('/deleteAPI') } /* put方法测试 */ export function fetchPut(data: any) { - return alovaInstance.Put('/putAPI', data) + return request.Put('/putAPI', data) } /* 不携带token的接口 */ export function withoutToken() { - const methodInstance = alovaInstance.Get('/getAPI') + const methodInstance = request.Get('/getAPI') methodInstance.meta = { authRole: null, } @@ -35,12 +35,16 @@ export function withoutToken() { } /* 接口数据转换 */ export function dictData() { - return alovaInstance.Get('/getDictData', { + return request.Get('/getDictData', { transformData(rawData, _headers) { - const { data } = rawData as any + const response = rawData as any return { - gender: data.gender === 0 ? '男' : '女', - status: `状态是${data.status}`, + ...response, + data: { + ...response.data, + gender: response.data.gender === 0 ? '男' : '女', + status: `状态是${response.data.status}`, + }, } }, }) @@ -57,29 +61,34 @@ export function getBlob(url: string) { /* 带进度的下载文件 */ export function downloadFile(url: string) { - return blankInstance.Get(url, { + const methodInstance = blankInstance.Get(url, { // 开启下载进度 enableDownload: true, }) + methodInstance.meta = { + // 标识为bolb数据 + isBlob: true, + } + return methodInstance } /* 测试状态码500失败 */ export function FailedRequest() { - return alovaInstance.Get('/serverError') + return request.Get('/serverError') } /* 测试业务码500失败 */ export function FailedResponse() { - return alovaInstance.Post('/businessError') + return request.Post('/businessError') } /* 测试业务码10000失败,无提示 */ export function FailedResponseWithoutTip() { - return alovaInstance.Post('/businessErrorWithoutTip') + return request.Post('/businessErrorWithoutTip') } /* token失效的接口 */ export function expiredTokenRequest() { - return alovaInstance.Get('/expiredToken') + return request.Get('/expiredToken') } /* 测试token刷新接口 */ export function refreshToken() { - return alovaInstance.Get('/updataToken') + return request.Get('/updataToken') } diff --git a/src/service/http/alova.ts b/src/service/http/alova.ts index ff5b1fc..c09d0db 100644 --- a/src/service/http/alova.ts +++ b/src/service/http/alova.ts @@ -30,7 +30,7 @@ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthenticati }, // 添加token到请求头 assignToken: (method) => { - method.config.headers.Authorization = `Bearer ${local.get('token')}` + method.config.headers.Authorization = `Bearer ${local.get('accessToken')}` }, }) diff --git a/src/service/http/config.ts b/src/service/http/config.ts index 67ea3bc..7cd4fe1 100644 --- a/src/service/http/config.ts +++ b/src/service/http/config.ts @@ -1,3 +1,4 @@ +import { $t } from '@/utils' /** 默认实例的Aixos配置 */ export const DEFAULT_ALOVA_OPTIONS = { // 请求超时时间,默认15秒 @@ -8,25 +9,25 @@ export const DEFAULT_ALOVA_OPTIONS = { export const DEFAULT_BACKEND_OPTIONS = { codeKey: 'code', dataKey: 'data', - msgKey: 'msg', + msgKey: 'message', successCode: 200, } /** 请求不成功各种状态的错误 */ export const ERROR_STATUS = { - default: '请求错误~', - 400: '400: 请求出现语法错误~', - 401: '401: 用户未授权~', - 403: '403: 服务器拒绝访问~', - 404: '404: 请求的资源不存在~', - 405: '405: 请求方法未允许~', - 408: '408: 网络请求超时~', - 500: '500: 服务器内部错误~', - 501: '501: 服务器未实现请求功能~', - 502: '502: 错误网关~', - 503: '503: 服务不可用~', - 504: '504: 网关超时~', - 505: '505: http版本不支持该请求~', + default: $t('http.defaultTip'), + 400: $t('http.400'), + 401: $t('http.401'), + 403: $t('http.403'), + 404: $t('http.404'), + 405: $t('http.405'), + 408: $t('http.408'), + 500: $t('http.500'), + 501: $t('http.501'), + 502: $t('http.502'), + 503: $t('http.503'), + 504: $t('http.504'), + 505: $t('http.505'), } /** 没有错误提示的code */ diff --git a/src/service/http/handle.ts b/src/service/http/handle.ts index b444d10..c9f7547 100644 --- a/src/service/http/handle.ts +++ b/src/service/http/handle.ts @@ -17,12 +17,12 @@ export function handleResponseError(response: Response) { const error: Service.RequestError = { errorType: 'Response Error', code: 0, - msg: ERROR_STATUS.default, + message: ERROR_STATUS.default, data: null, } const errorCode: ErrorStatus = response.status as ErrorStatus - const msg = ERROR_STATUS[errorCode] || ERROR_STATUS.default - Object.assign(error, { code: errorCode, msg }) + const message = ERROR_STATUS[errorCode] || ERROR_STATUS.default + Object.assign(error, { code: errorCode, message }) showError(error) @@ -40,7 +40,7 @@ export function handleBusinessError(data: Record, config: Required< const error: Service.RequestError = { errorType: 'Business Error', code: data[codeKey], - msg: data[msgKey], + message: data[msgKey], data: data.data, } @@ -56,11 +56,12 @@ export function handleBusinessError(data: Record, config: Required< * @return {*} result */ export function handleServiceResult(data: any, isSuccess: boolean = true) { - return { + const result = { isSuccess, errorType: null, ...data, } + return result } /** @@ -69,9 +70,9 @@ export function handleServiceResult(data: any, isSuccess: boolean = true) { */ export async function handleRefreshToken() { const authStore = useAuthStore() - const data = await fetchUpdateToken({ refreshToken: local.get('refreshToken') }) + const { data } = await fetchUpdateToken({ refreshToken: local.get('refreshToken') }) if (data) { - local.set('token', data.accessToken) + local.set('accessToken', data.accessToken) local.set('refreshToken', data.refreshToken) } else { @@ -86,5 +87,5 @@ export function showError(error: Service.RequestError) { if (ERROR_NO_TIP_STATUS.includes(code)) return - window.$message.error(error.msg) + window.$message.error(error.message) } diff --git a/src/service/http/index.ts b/src/service/http/index.ts index 988ccf4..f1ba12c 100644 --- a/src/service/http/index.ts +++ b/src/service/http/index.ts @@ -2,11 +2,11 @@ import { createAlovaInstance } from './alova' import { serviceConfig } from '@/../service.config' import { generateProxyPattern } from '@/../build/proxy' -const isHttpProxy = import.meta.env.VITE_HTTP_PROXY === 'Y' || false +const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y' const { url } = generateProxyPattern(serviceConfig[import.meta.env.MODE]) -export const alovaInstance = createAlovaInstance({ +export const request = createAlovaInstance({ baseURL: isHttpProxy ? url.proxy : url.value, }) diff --git a/src/service/index.ts b/src/service/index.ts index ff23946..d292e81 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -1,3 +1,4 @@ -export * from './api/list' +export * from './api/system' export * from './api/login' +export * from './api/list' export * from './api/test' diff --git a/src/store/app/index.ts b/src/store/app/index.ts index 4b98ab1..4b5cc4b 100644 --- a/src/store/app/index.ts +++ b/src/store/app/index.ts @@ -1,9 +1,11 @@ import type { GlobalThemeOverrides } from 'naive-ui' -import chroma from 'chroma-js' +import { colord } from 'colord' import { set } from 'radash' import themeConfig from './theme.json' +import { local, setLocale } from '@/utils' type TransitionAnimation = '' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out' +export type LayoutMode = 'leftMenu' | 'topMenu' const docEle = ref(document.documentElement) @@ -17,6 +19,7 @@ export const useAppStore = defineStore('app-store', { state: () => { return { footerText: 'Copyright © 2024 chansee97', + lang: 'enUS' as App.lang, theme: themeConfig as GlobalThemeOverrides, primaryColor: themeConfig.common.primaryColor, collapsed: false, @@ -31,6 +34,7 @@ export const useAppStore = defineStore('app-store', { showBreadcrumbIcon: true, showWatermark: false, transitionAnimation: 'fade-slide' as TransitionAnimation, + layoutMode: 'leftMenu' as LayoutMode, } }, getters: { @@ -61,15 +65,20 @@ export const useAppStore = defineStore('app-store', { this.showBreadcrumbIcon = true this.showWatermark = false this.transitionAnimation = 'fade-slide' + this.layoutMode = 'leftMenu' // 重置所有配色 this.setPrimaryColor(this.primaryColor) }, + setAppLang(lang: App.lang) { + setLocale(lang) + local.set('lang', lang) + this.lang = lang + }, /* 设置主题色 */ setPrimaryColor(color: string) { - docEle.value.style.setProperty('--primary-color', color) - const brightenColor = chroma(color).brighten(1).hex() - const darkenColor = chroma(color).darken(1).hex() + const brightenColor = colord(color).lighten(0.1).toHex() + const darkenColor = colord(color).darken(0.05).toHex() set(this.theme, 'common.primaryColor', color) set(this.theme, 'common.primaryColorHover', brightenColor) set(this.theme, 'common.primaryColorPressed', darkenColor) diff --git a/src/store/auth.ts b/src/store/auth.ts index 1c39f7a..bcb5d71 100644 --- a/src/store/auth.ts +++ b/src/store/auth.ts @@ -12,7 +12,7 @@ export const useAuthStore = defineStore('auth-store', { state: (): AuthStatus => { return { userInfo: local.get('userInfo'), - token: local.get('token') || '', + token: local.get('accessToken') || '', } }, getters: { @@ -46,15 +46,15 @@ export const useAuthStore = defineStore('auth-store', { } }, clearAuthStorage() { - local.remove('token') + local.remove('accessToken') local.remove('refreshToken') local.remove('userInfo') }, /* 用户登录 */ - async login(username: string, password: string) { - const { error, data } = await fetchLogin({ username, password }) - if (error) + async login(userName: string, password: string) { + const { isSuccess, data } = await fetchLogin({ userName, password }) + if (!isSuccess) return // 处理登录信息 @@ -65,7 +65,7 @@ export const useAuthStore = defineStore('auth-store', { async handleAfterLogin(data: ApiAuth.loginInfo) { // 将token和userInfo保存下来 local.set('userInfo', data) - local.set('token', data.accessToken) + local.set('accessToken', data.accessToken) local.set('refreshToken', data.refreshToken) this.token = data.accessToken this.userInfo = data diff --git a/src/store/route.ts b/src/store/route.ts index 8c61da6..e2b22de 100644 --- a/src/store/route.ts +++ b/src/store/route.ts @@ -3,12 +3,12 @@ import { RouterLink } from 'vue-router' import { h } from 'vue' import { clone, construct, min } from 'radash' import type { RouteRecordRaw } from 'vue-router' -import { arrayToTree, local, renderIcon } from '@/utils' +import { $t, arrayToTree, local, renderIcon } from '@/utils' import { router } from '@/router' import { fetchUserRoutes } from '@/service' import { staticRoutes } from '@/router/routes.static' import { usePermission } from '@/hooks' -import { BasicLayout } from '@/layouts/index' +import Layout from '@/layouts/index.vue' import { useAuthStore } from '@/store/auth' interface RoutesStatus { @@ -34,29 +34,29 @@ export const useRouteStore = defineStore('route-store', { this.$reset() }, resetRoutes() { - /* 删除后面添加的路由 */ router.removeRoute('appRoot') }, - /* 设置当前高亮的菜单key */ + // set the currently highlighted menu key setActiveMenu(key: string) { this.activeMenu = key }, /* 生成侧边菜单的数据 */ createMenus(userRoutes: AppRoute.RowRoute[]) { const resultMenus = clone(userRoutes).map(i => construct(i)) as AppRoute.Route[] - // arrayToTree2() - /** 过滤不需要显示的菜单 */ + + // filter menus that do not need to be displayed const visibleMenus = resultMenus.filter(route => !route.meta.hide) - // 生成侧边菜单 + + // generate side menu this.menus = arrayToTree(this.transformAuthRoutesToMenus(visibleMenus)) }, - //* 将返回的路由表渲染成侧边栏 */ + // render the returned routing table as a sidebar transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] { const { hasPermission } = usePermission() - /** 过滤没有权限的侧边菜单 */ + // Filter out side menus without permission return userRoutes.filter(i => hasPermission(i.meta.roles)) - /** 根据order大小菜单排序 */ + // Sort the menu according to the order size .sort((a, b) => { if (a.meta && a.meta.order && b.meta && b.meta.order) return a.meta.order - b.meta.order @@ -66,13 +66,14 @@ export const useRouteStore = defineStore('route-store', { return 1 else return 0 }) - /** 转换为侧边菜单数据结构 */ + + // Convert to side menu data structure .map((item) => { const target: MenuOption = { id: item.id, pid: item.pid, label: - (!item.children || item.children.length === 0) + (!item.meta.menuType || item.meta.menuType === 'page') ? () => h( RouterLink, @@ -81,30 +82,75 @@ export const useRouteStore = defineStore('route-store', { path: item.path, }, }, - { default: () => item.meta.title }, + { default: () => $t(`route.${String(item.name)}`, item.meta.title) }, ) - : item.meta.title, + : () => $t(`route.${String(item.name)}`, item.meta.title), key: item.path, icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined, } return target }) }, + createRoutes(routes: AppRoute.RowRoute[]) { + const { hasPermission } = usePermission() + + // Structure the meta field + let resultRouter = clone(routes).map(i => construct(i)) as AppRoute.Route[] + + // Route permission filtering + resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles)) + + // Generate an array of route names that need to be kept alive + this.cacheRoutes = resultRouter.filter((i) => { + return i.meta.keepAlive + }) + .map(i => i.name) + + // Generate routes, no need to import files for those with redirect + const modules = import.meta.glob('@/views/**/*.vue') + resultRouter = resultRouter.map((item: AppRoute.Route) => { + if (item.componentPath && !item.redirect) + item.component = modules[`/src/views${item.componentPath}`] + return item + }) + + // Generate route tree + resultRouter = arrayToTree(resultRouter) as AppRoute.Route[] + + const appRootRoute: RouteRecordRaw = { + path: '/appRoot', + name: 'appRoot', + redirect: import.meta.env.VITE_HOME_PATH, + component: Layout, + meta: { + title: '', + icon: 'icon-park-outline:home', + }, + children: [], + } + + // Set the correct redirect path for the route + this.setRedirect(resultRouter) + + // Insert the processed route into the root route + appRootRoute.children = resultRouter as unknown as RouteRecordRaw[] + router.addRoute(appRootRoute) + }, setRedirect(routes: AppRoute.Route[]) { routes.forEach((route) => { if (route.children) { if (!route.redirect) { - // 过滤出没有隐藏的子元素集 + // Filter out a collection of child elements that are not hidden const visibleChilds = route.children.filter(child => !child.meta.hide) - // 过滤出含有order属性的页面 + // Redirect page to the path of the first child element by default + let target = visibleChilds[0] + + // Filter out pages with the order attribute const orderChilds = visibleChilds.filter(child => child.meta.order) - // 重定向页默认第一个子元素的路径 - let target = route.children[0] if (orderChilds.length > 0) - // 有order则取最小者重定向 - target = min(orderChilds, i => i.meta.order as number) as AppRoute.Route + target = min(orderChilds, i => i.meta.order!) as AppRoute.Route route.redirect = target.path } @@ -113,48 +159,8 @@ export const useRouteStore = defineStore('route-store', { } }) }, - createRoutes(routes: AppRoute.RowRoute[]) { - const { hasPermission } = usePermission() - // 结构化meta字段 - let resultRouter = clone(routes).map(i => construct(i)) as AppRoute.Route[] - // 路由权限过滤 - resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles)) - - // 生成需要keepAlive的路由name数组 - this.cacheRoutes = resultRouter.filter((i) => { - return i.meta.keepAlive - }) - .map(i => i.name) - - // 生成路由,有redirect的不需要引入文件 - const modules = import.meta.glob('@/views/**/*.vue') - resultRouter = resultRouter.map((item: any) => { - if (item.componentPath && !item.redirect) - item.component = modules[`/src/views${item.componentPath}`] - return item - }) - - resultRouter = arrayToTree(resultRouter) as AppRoute.Route[] - this.setRedirect(resultRouter) - const appRootRoute: RouteRecordRaw = { - path: '/appRoot', - name: 'appRoot', - redirect: import.meta.env.VITE_HOME_PATH, - component: BasicLayout, - meta: { - title: '首页', - icon: 'icon-park-outline:home', - }, - children: [], - } - // 根据角色过滤后的插入根路由中 - appRootRoute.children = resultRouter as unknown as RouteRecordRaw[] - // 插入路由表 - router.addRoute(appRootRoute) - }, async initRouteInfo() { if (import.meta.env.VITE_AUTH_ROUTE_MODE === 'dynamic') { - // 根据用户id来获取用户的路由 const userInfo = local.get('userInfo') if (!userInfo || !userInfo.id) { @@ -163,6 +169,7 @@ export const useRouteStore = defineStore('route-store', { return } + // Get user's route const { data } = await fetchUserRoutes({ id: userInfo.id, }) @@ -179,12 +186,19 @@ export const useRouteStore = defineStore('route-store', { }, async initAuthRoute() { this.isInitAuthRoute = false - // 初始化路由信息 + + // Initialize route information const rowRoutes = await this.initRouteInfo() + if (!rowRoutes) { + window.$message.error($t(`app.getRouteError`)) + return + } this.rowRoutes = rowRoutes - // 生成真实路由并插入 + + // Generate actual route and insert this.createRoutes(rowRoutes) - // 生成侧边菜单 + + // Generate side menu this.createMenus(rowRoutes) this.isInitAuthRoute = true diff --git a/src/store/tab.ts b/src/store/tab.ts index 6e79ef7..047019f 100644 --- a/src/store/tab.ts +++ b/src/store/tab.ts @@ -100,7 +100,7 @@ export const useTabStore = defineStore('tab-store', { enabled: true, strategies: [ { - storage: localStorage, + storage: sessionStorage, }, ], }, diff --git a/src/styles/index.css b/src/styles/index.css index 7991a1f..c09a6a2 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -1,6 +1,5 @@ @import './reset.css'; @import './transition.css'; -@import './nprogress.css'; html, body, diff --git a/src/styles/nprogress.css b/src/styles/nprogress.css deleted file mode 100644 index c3ad48c..0000000 --- a/src/styles/nprogress.css +++ /dev/null @@ -1,84 +0,0 @@ -/* Make clicks pass-through */ -#nprogress { - pointer-events: none; -} - -#nprogress .bar { - background: var(--primary-color); - - position: fixed; - z-index: 1031; - top: 0; - left: 0; - - width: 100%; - height: 2px; -} - -/* Fancy blur effect */ -#nprogress .peg { - display: block; - position: absolute; - right: 0px; - width: 100px; - height: 100%; - box-shadow: 0 0 10px var(--primary-color), 0 0 5px var(--primary-color); - opacity: 1.0; - - -webkit-transform: rotate(3deg) translate(0px, -4px); - -ms-transform: rotate(3deg) translate(0px, -4px); - transform: rotate(3deg) translate(0px, -4px); -} - -/* Remove these to get rid of the spinner */ -#nprogress .spinner { - display: block; - position: fixed; - z-index: 1031; - top: 15px; - right: 15px; -} - -#nprogress .spinner-icon { - width: 18px; - height: 18px; - box-sizing: border-box; - - border: solid 2px transparent; - border-top-color: var(--primary-color); - border-left-color: var(--primary-color); - border-radius: 50%; - - -webkit-animation: nprogress-spinner 400ms linear infinite; - animation: nprogress-spinner 400ms linear infinite; -} - -.nprogress-custom-parent { - overflow: hidden; - position: relative; -} - -.nprogress-custom-parent #nprogress .spinner, -.nprogress-custom-parent #nprogress .bar { - position: absolute; -} - -@-webkit-keyframes nprogress-spinner { - 0% { - -webkit-transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - } -} - -@keyframes nprogress-spinner { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} diff --git a/src/typings/api.d.ts b/src/typings/api.d.ts index cfd98d6..e561316 100644 --- a/src/typings/api.d.ts +++ b/src/typings/api.d.ts @@ -7,18 +7,20 @@ declare namespace ApiAuth { /** 用户id */ id: number /** 用户名 */ - username: string + userName: string /* 用户头像 */ avatar?: string /* 用户邮箱 */ email?: string /* 用户昵称 */ nickname?: string - notes?: string + /* 用户电话 */ tel?: string /** 用户角色类型 */ role: Auth.RoleType + /** 访问toekn */ accessToken: string + /** 刷新toekn */ refreshToken: string } } diff --git a/src/typings/env.d.ts b/src/typings/env.d.ts index d464233..1c96aee 100644 --- a/src/typings/env.d.ts +++ b/src/typings/env.d.ts @@ -14,7 +14,7 @@ interface ImportMetaEnv { /** 开启请求代理 */ readonly VITE_HTTP_PROXY?: 'Y' | 'N' /** 是否开启打包压缩 */ - readonly VITE_COMPRESS_OPEN?: 'Y' | 'N' + readonly VITE_BUILD_COMPRESS?: 'Y' | 'N' /** 压缩算法类型 */ readonly VITE_COMPRESS_TYPE?: | 'gzip' diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts index 99650e4..96982e2 100644 --- a/src/typings/global.d.ts +++ b/src/typings/global.d.ts @@ -3,7 +3,6 @@ interface Window { $dialog: import('naive-ui').DialogApi $message: import('naive-ui').MessageApi $notification: import('naive-ui').NotificationApi - $NProgress: import('NProgress').NProgress } declare const AMap: any @@ -26,10 +25,19 @@ declare namespace Storage { } interface Local { + /* 存储用户信息 */ userInfo: ApiAuth.loginInfo - token: string + /* 存储访问token */ + accessToken: string + /* 存储刷新token */ refreshToken: string - tabsRoutes: string - login_account: any + /* 存储登录账号 */ + loginAccount: any + /* 存储当前语言 */ + lang: App.lang } } + +declare namespace App { + type lang = 'zhCN' | 'enUS' +} diff --git a/src/typings/package.d.ts b/src/typings/package.d.ts deleted file mode 100644 index 3643543..0000000 --- a/src/typings/package.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare module '~icons/*' { - import type { FunctionalComponent, SVGAttributes } from 'vue' - - const component: FunctionalComponent - export default component -} diff --git a/src/typings/route.d.ts b/src/typings/route.d.ts index 396135a..76cdec0 100644 --- a/src/typings/route.d.ts +++ b/src/typings/route.d.ts @@ -1,7 +1,9 @@ declare namespace AppRoute { - /** 路由描述 */ + + type MenuType = 'dir' | 'page' + /** 单个路由所携带的meta标识 */ interface RouteMeta { - /* 页面标题,通常必选。 */ + /* 页面标题,通常必选。 */ title: string /* 图标,一般配合菜单使用 */ icon?: string @@ -17,14 +19,16 @@ declare namespace AppRoute { order?: number /* 嵌套外链 */ herf?: string - /** 当前路由需要选中的菜单项(用于跳转至不在左侧菜单显示的路由且需要高亮某个菜单的情况) */ + /** 当前路由不在左侧菜单显示,但需要高亮某个菜单的情况 */ activeMenu?: string /** 当前路由是否会被添加到Tab中 */ withoutTab?: boolean /** 当前路由是否会被固定在Tab中,用于一些常驻页面 */ pinTab?: boolean + /** 当前路由在左侧菜单是目录还是页面,不设置默认为page */ + menuType?: MenuType } - /** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */ + interface baseRoute { /** 路由名称(路由唯一标识) */ name: string @@ -34,16 +38,20 @@ declare namespace AppRoute { redirect?: string /* 页面组件地址 */ componentPath?: string | null - // 路由id + /* 路由id */ id: numnber - // 父级路由id,顶级页面为null + /* 父级路由id,顶级页面为null */ pid: number | null } + /** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */ type RowRoute = { [K in keyof RouteMeta as `meta.${K}`]?: RouteMeta[K] } & baseRoute + /** + * 挂载到项目上的真实路由结构 + */ interface Route extends baseRoute { /** 子路由 */ children?: Route[] diff --git a/src/typings/service.d.ts b/src/typings/service.d.ts index 888fe56..ef0439e 100644 --- a/src/typings/service.d.ts +++ b/src/typings/service.d.ts @@ -20,7 +20,7 @@ declare namespace Service { successCode?: number | string } - type RequestErrorType = 'Response Error' | 'Business Error' + type RequestErrorType = 'Response Error' | 'Business Error' | null type RequestCode = string | number interface RequestError { @@ -29,8 +29,21 @@ declare namespace Service { /** 错误码 */ code: RequestCode /** 错误信息 */ - msg: string + message: string /** 返回的数据 */ data?: any } + + interface ResponseResult extends RequestError { + /** 请求服务是否成功 */ + isSuccess: boolean + /** 请求服务的错误类型 */ + errorType: RequestErrorType + /** 错误码 */ + code: RequestCode + /** 错误信息 */ + message: string + /** 返回的数据 */ + data: T + } } diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts new file mode 100644 index 0000000..27f18c9 --- /dev/null +++ b/src/utils/i18n.ts @@ -0,0 +1,20 @@ +import type { NDateLocale, NLocale } from 'naive-ui' +import { dateZhCN, zhCN } from 'naive-ui' +import { i18n } from '@/modules/i18n' + +export function setLocale(locale: App.lang) { + i18n.global.locale.value = locale +} + +export const $t = i18n.global.t + +export const naiveI18nOptions: Record = { + zhCN: { + locale: zhCN, + dateLocale: dateZhCN, + }, + enUS: { + locale: null, + dateLocale: null, + }, +} diff --git a/src/utils/icon.ts b/src/utils/icon.ts index a4c8fe0..cfccc55 100644 --- a/src/utils/icon.ts +++ b/src/utils/icon.ts @@ -1,6 +1,6 @@ import { Icon } from '@iconify/vue' import { NIcon } from 'naive-ui' -export function renderIcon(icon: string) { - return () => h(NIcon, null, { default: () => h(Icon, { icon }) }) +export function renderIcon(icon: string, props?: import('naive-ui').IconProps) { + return () => h(NIcon, props, { default: () => h(Icon, { icon }) }) } diff --git a/src/utils/index.ts b/src/utils/index.ts index c8551d7..d9bedb9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './icon' export * from './storage' export * from './array' +export * from './i18n' diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 83f8574..281fb51 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,4 +1,4 @@ -const STORAGE_PREFIX = 'nova_' +const STORAGE_PREFIX = import.meta.env.VITE_STORAGE_PREFIX interface StorageData { value: T diff --git a/src/views/dashboard/monitor/components/chart.vue b/src/views/dashboard/monitor/components/chart.vue new file mode 100644 index 0000000..72911a7 --- /dev/null +++ b/src/views/dashboard/monitor/components/chart.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/src/views/dashboard/monitor/components/chart2.vue b/src/views/dashboard/monitor/components/chart2.vue new file mode 100644 index 0000000..e16ed50 --- /dev/null +++ b/src/views/dashboard/monitor/components/chart2.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/views/dashboard/monitor/components/chart3.vue b/src/views/dashboard/monitor/components/chart3.vue new file mode 100644 index 0000000..0bc6bd2 --- /dev/null +++ b/src/views/dashboard/monitor/components/chart3.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/views/dashboard/monitor/index.vue b/src/views/dashboard/monitor/index.vue index 7baff0b..4de8f33 100644 --- a/src/views/dashboard/monitor/index.vue +++ b/src/views/dashboard/monitor/index.vue @@ -1,4 +1,8 @@ + + + + diff --git a/src/views/demo/fetch/components/DownLoad.vue b/src/views/demo/fetch/components/DownLoad.vue new file mode 100644 index 0000000..22fa77a --- /dev/null +++ b/src/views/demo/fetch/components/DownLoad.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/views/demo/fetch/components/DownLoadWithProgress.vue b/src/views/demo/fetch/components/DownLoadWithProgress.vue new file mode 100644 index 0000000..8697897 --- /dev/null +++ b/src/views/demo/fetch/components/DownLoadWithProgress.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/views/demo/fetch/components/Env.vue b/src/views/demo/fetch/components/Env.vue new file mode 100644 index 0000000..d9604fc --- /dev/null +++ b/src/views/demo/fetch/components/Env.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/views/demo/fetch/components/FailedRequest.vue b/src/views/demo/fetch/components/FailedRequest.vue new file mode 100644 index 0000000..7553485 --- /dev/null +++ b/src/views/demo/fetch/components/FailedRequest.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/FailedResponse.vue b/src/views/demo/fetch/components/FailedResponse.vue new file mode 100644 index 0000000..91b5f01 --- /dev/null +++ b/src/views/demo/fetch/components/FailedResponse.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/FailedResponseWithoutTip.vue b/src/views/demo/fetch/components/FailedResponseWithoutTip.vue new file mode 100644 index 0000000..6fca635 --- /dev/null +++ b/src/views/demo/fetch/components/FailedResponseWithoutTip.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/FormPost.vue b/src/views/demo/fetch/components/FormPost.vue new file mode 100644 index 0000000..64b5c04 --- /dev/null +++ b/src/views/demo/fetch/components/FormPost.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/views/demo/fetch/components/Get.vue b/src/views/demo/fetch/components/Get.vue new file mode 100644 index 0000000..3294639 --- /dev/null +++ b/src/views/demo/fetch/components/Get.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/NoToken.vue b/src/views/demo/fetch/components/NoToken.vue new file mode 100644 index 0000000..ece15d5 --- /dev/null +++ b/src/views/demo/fetch/components/NoToken.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/Post.vue b/src/views/demo/fetch/components/Post.vue new file mode 100644 index 0000000..e9266c0 --- /dev/null +++ b/src/views/demo/fetch/components/Post.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/views/demo/fetch/components/Put.vue b/src/views/demo/fetch/components/Put.vue new file mode 100644 index 0000000..942c7ca --- /dev/null +++ b/src/views/demo/fetch/components/Put.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/RefreshToken.vue b/src/views/demo/fetch/components/RefreshToken.vue new file mode 100644 index 0000000..a024314 --- /dev/null +++ b/src/views/demo/fetch/components/RefreshToken.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/TokenExpiration.vue b/src/views/demo/fetch/components/TokenExpiration.vue new file mode 100644 index 0000000..1b03181 --- /dev/null +++ b/src/views/demo/fetch/components/TokenExpiration.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/views/demo/fetch/components/Transform.vue b/src/views/demo/fetch/components/Transform.vue new file mode 100644 index 0000000..1cb85f0 --- /dev/null +++ b/src/views/demo/fetch/components/Transform.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/demo/fetch/components/UseRequest.vue b/src/views/demo/fetch/components/UseRequest.vue new file mode 100644 index 0000000..3f8035d --- /dev/null +++ b/src/views/demo/fetch/components/UseRequest.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/views/demo/fetch/index.vue b/src/views/demo/fetch/index.vue new file mode 100644 index 0000000..a18b094 --- /dev/null +++ b/src/views/demo/fetch/index.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/views/demo/icons/index.vue b/src/views/demo/icons/index.vue new file mode 100644 index 0000000..ae72c9d --- /dev/null +++ b/src/views/demo/icons/index.vue @@ -0,0 +1,29 @@ + diff --git a/src/views/plugin/map/components/AMap.vue b/src/views/demo/map/components/AMap.vue similarity index 100% rename from src/views/plugin/map/components/AMap.vue rename to src/views/demo/map/components/AMap.vue diff --git a/src/views/plugin/map/components/BMap.vue b/src/views/demo/map/components/BMap.vue similarity index 100% rename from src/views/plugin/map/components/BMap.vue rename to src/views/demo/map/components/BMap.vue diff --git a/src/views/plugin/map/index.vue b/src/views/demo/map/index.vue similarity index 100% rename from src/views/plugin/map/index.vue rename to src/views/demo/map/index.vue diff --git a/src/views/list/cardList/index.vue b/src/views/list/cardList/index.vue index d213f81..6f915e2 100644 --- a/src/views/list/cardList/index.vue +++ b/src/views/list/cardList/index.vue @@ -104,7 +104,7 @@ const radioDate = [ color="#de4307" size="24" > - +